ESP32 and MicroPython: Getting Your First Device Talking to a VPS

Quick answer

Every other guide on this site assumes you already have a device sending data to your VPS. This one is the missing first step: an ESP32 microcontroller, a few pounds of hardware, running MicroPython, reading a sensor and publishing it over MQTT to your VPS’s Mosquitto broker, the entire chain from bare metal to dashboard in roughly an hour for anyone comfortable following step-by-step instructions.

Why the ESP32 specifically

For anyone building their first genuinely custom IoT device, rather than buying something pre-made, the ESP32 is the natural starting point: a few pounds for the board itself, built-in Wi-Fi, enough processing power to run MicroPython comfortably, and a vast, well-documented community around exactly this use case. This guide assumes the Mosquitto MQTT broker from this site’s self-hosting guides is already running on your VPS, since this page is specifically about the device side that everything else on this site has been assuming exists.

ESP32 MicroPython

Wi-Fi umqtt.simple

YOUR VPS Mosquitto

Node-RED / Grafana

One sensor reading, published as one MQTT message, every time round the loop

Flashing MicroPython onto a fresh board

pip install esptool --break-system-packages
esptool.py --port /dev/ttyUSB0 erase_flash
esptool.py --port /dev/ttyUSB0 --baud 460800 write_flash 0x1000 
  ESP32_GENERIC-20260115-v1.24.0.bin

Download the correct firmware build for your specific ESP32 variant from the official MicroPython downloads page first, the exact filename above is illustrative; boards vary (plain ESP32, ESP32-S3, ESP32-C3) and using the wrong build is the most common source of early frustration. Once flashed, connecting via a serial terminal (Thonny, the free MicroPython-aware IDE, is the easiest starting point for beginners) gives you a live MicroPython REPL directly on the board.

Connecting to Wi-Fi

import network
import time

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('your-wifi-ssid', 'your-wifi-password')

while not wlan.isconnected():
    time.sleep(0.5)

print('Connected:', wlan.ifconfig())

Publishing your first MQTT message

from umqtt.simple import MQTTClient
import time

client = MQTTClient('esp32-sensor-01', 'your-vps-ip-or-domain', port=1883,
                     user='your-mqtt-username', password='your-mqtt-password')
client.connect()

while True:
    temperature = 21.5  # replace with a real sensor reading
    client.publish('sensors/esp32-01/temperature', str(temperature))
    time.sleep(60)

umqtt.simple is the standard, lightweight MQTT client library for MicroPython, not bundled by default but easy to add via mip.install('umqtt.simple') from the REPL on boards with internet access, or copied manually onto the board’s filesystem. The username and password here match whatever authentication you configured when setting up Mosquitto in this site’s broker guide, the same credential discipline applies to a microcontroller as to any other client.

Adding a real sensor

A DS18B20 (temperature) or DHT22 (temperature and humidity) are the most common, cheapest, best-documented starting sensors, both well supported by existing MicroPython libraries rather than needing to write low-level driver code from scratch:

import dht
from machine import Pin

sensor = dht.DHT22(Pin(4))
sensor.measure()
temperature = sensor.temperature()
humidity = sensor.humidity()

Swap the hardcoded temperature = 21.5 line in the MQTT example above for a real sensor read, and the chain from physical sensor to your VPS’s dashboard, covered in Grafana + InfluxDB on a VPS, is complete.

Power: the genuinely important consideration most beginner tutorials skip

An ESP32 left permanently awake and connected to Wi-Fi draws meaningfully more power than most people expect, fine for a mains-powered project but a real problem for anything intended to run on battery. For a battery project, deep sleep between readings is essential, not optional:

import machine
machine.deepsleep(60000)  # sleep for 60 seconds, then restart the script

Worth knowing as a genuine gotcha: deep sleep restarts your entire script from the top each time, not resuming from where it paused, meaning Wi-Fi and MQTT need reconnecting on every wake cycle. This is normal, expected behaviour, not a bug, and is the standard pattern for genuinely battery-efficient ESP32 deployments.

A realistic first project to actually build

Rather than reading this guide and not building anything, a sensible, achievable first project: one ESP32, one DHT22 sensor, mains powered, sitting somewhere in your home, publishing a reading every minute to your VPS, visible on a simple Grafana panel within the hour. This is deliberately the same scope as the worked examples covered in several of this site’s other guides, small enough to genuinely finish in one sitting, with everything else, battery operation, multiple sensors, enclosures, additive once this first working chain is solid and trusted.

Where this connects to the rest of this site

Once a device is reliably publishing to MQTT, everything downstream, Node-RED automation logic, ThingsBoard dashboards, alerting via the patterns in SMS and Email Alerting for IoT Devices, works identically regardless of whether the data originated from a £5 ESP32 you programmed yourself or an expensive commercial sensor. This is genuinely the foundational, missing first chapter underneath most of this site’s other content, worth bookmarking for anyone building their first custom device rather than buying one ready-made.

Frequently asked questions

Is MicroPython fast enough for genuinely real-time applications?

For typical sensor-reading-and-publishing tasks at intervals of seconds to minutes, comfortably yes; for genuinely hard real-time requirements (precise timing measured in microseconds), C/C++ via the ESP-IDF or Arduino framework is the more appropriate choice, but this is well beyond what most projects on this site actually need.

Can the same ESP32 run multiple sensors and publish multiple MQTT topics?

Yes, easily, simply read each sensor and call client.publish() with a distinct topic per sensor, a common, practical pattern for a single board monitoring several things in one location.

What happens if the ESP32 loses Wi-Fi or MQTT connection temporarily?

Worth handling explicitly in your own code with a reconnection loop and basic error handling around the connect calls, since an unhandled connection drop will otherwise crash the script; this is genuinely one of the most common real-world issues worth testing for deliberately before trusting a device unattended.

Is TLS-encrypted MQTT (port 8883) practical on an ESP32’s limited hardware?

Yes, modern ESP32 variants handle TLS comfortably, covered in more depth in MQTT Security: TLS, Authentication and ACLs Done Properly, worth implementing for anything beyond a purely local hobby test given how straightforward plain MQTT credentials are to intercept on an unencrypted connection.

Do I need to buy an expensive ESP32 development board, or will the cheapest one work?

The cheapest generic ESP32 boards, widely available for just a few pounds, are entirely adequate for everything covered in this guide; the more expensive variants add features (more memory, additional peripherals, camera support) genuinely useful for specific advanced projects but unnecessary for a first device.

Can I use Arduino IDE instead of MicroPython for the same VPS architecture?

Yes, the underlying architecture, device to MQTT to VPS, is identical regardless of firmware choice; Arduino’s C/C++ environment is a perfectly valid alternative to MicroPython, generally offering slightly better performance at the cost of a steeper learning curve for beginners, a reasonable trade-off to make based on your own comfort with each language.

Structuring your code for a device that runs unattended

A device running MicroPython in a lab, where you can see the serial output and reset it if something goes wrong, behaves very differently from the same device sealed in an enclosure and expected to run reliably for months. The most common gap: exception handling around the MQTT publish call, so a transient network failure doesn’t silently crash the script and leave the device appearing online but actually doing nothing. A minimal, robust main loop pattern:

while True:
    try:
        sensor.measure()
        client.publish('sensors/device-01/temp', str(sensor.temperature()))
        time.sleep(60)
    except Exception as e:
        print('Error:', e)
        time.sleep(30)
        machine.reset()  # hard reset and reconnect on any unhandled exception

The hard reset on exception is aggressive but practical: MicroPython’s memory management means long-running scripts can drift into strange states after repeated errors, and a clean restart is often faster and more reliable than trying to recover gracefully from every possible failure mode on constrained hardware.

Moving from one device to many

Once a single device is reliable, adding a second is the moment to establish naming conventions that scale: topic structures like sensors/{location}/{device-id}/{measurement} are considerably easier to extend later than ad hoc names invented device by device. The same principle applies to the device IDs themselves, short, unique, human-readable identifiers, a label matching the physical device, spare you considerable confusion once a dozen devices are deployed and you need to correlate a dashboard alert with a physical location.

Provisioning multiple devices without reflashing each one individually

For anyone moving beyond a handful of devices, reflashing and individually configuring each one becomes a real bottleneck. A practical alternative: store Wi-Fi credentials and MQTT connection details in a configuration file on the device’s filesystem rather than hardcoded in the script itself, then use MicroPython’s filesystem access to read them on startup. This means flashing the same base firmware to every device, then uploading a small per-device config file with just the device-specific settings, a much faster provisioning pattern once it’s established.

# config.py on the device filesystem
WIFI_SSID = 'your-network'
WIFI_PASSWORD = 'your-password'
MQTT_BROKER = 'your-vps-domain'
MQTT_USER = 'device-kitchen-01'
MQTT_PASSWORD = 'device-specific-password'
DEVICE_ID = 'kitchen-01'
# main.py imports from config rather than hardcoding
from config import WIFI_SSID, WIFI_PASSWORD, MQTT_BROKER, MQTT_USER, MQTT_PASSWORD, DEVICE_ID
# rest of the script uses these variables directly

This also means rotating an individual device’s MQTT credentials only requires updating that device’s config.py and rebooting, without changing the base firmware at all, the same separation between infrastructure configuration and application code that this site’s other guides apply at the VPS level.

Connecting to Node-RED and seeing the data in Grafana

With a device reliably publishing to MQTT, the next step in this site’s architecture is straightforward: a Node-RED MQTT-in node subscribed to sensors/# receives every message, routes it based on topic structure, and passes it to a InfluxDB write node. The payload needs to be a number (not a string) for InfluxDB to store it correctly as a measurement rather than a tag, which is why the str(temperature) in the MicroPython code becomes parseFloat(msg.payload) in the Node-RED function node that processes it. From there, the Grafana dashboard covered in Grafana + InfluxDB on a VPS picks it up directly, and the first end-to-end chain from physical sensor to visible graph is complete.

Frequently asked questions

Is MicroPython fast enough for real-time applications?

For typical sensor-reading-and-publishing tasks at intervals of seconds to minutes, comfortably yes. For hard real-time requirements measured in microseconds, C/C++ via Arduino or ESP-IDF is more appropriate, but this is well beyond what most projects on this site actually need.

Can the same ESP32 run multiple sensors?

Yes, easily. Read each sensor and call client.publish() with a distinct topic per reading, a common pattern for a single board monitoring several things in one location.

What happens if the ESP32 loses Wi-Fi or MQTT connection temporarily?

Worth handling explicitly with a reconnection loop and error handling around the connect calls. An unhandled connection drop will otherwise silently crash the script, the most common real-world issue worth testing for deliberately before deploying a device unattended.

Is TLS-encrypted MQTT practical on an ESP32?

Yes, modern ESP32 variants handle TLS comfortably. See MQTT Security: TLS, Authentication and ACLs for the broker-side configuration and the CA certificate your devices will need to verify the connection.

Do I need an expensive development board or will a cheap one work?

The cheapest generic ESP32 boards, available for a few pounds, are entirely adequate for everything covered in this guide. More expensive variants add features useful for specific advanced projects but unnecessary for a first device.

OTA updates: keeping devices current without physical access

One genuinely important consideration for any deployment of more than a handful of ESP32 devices: how do firmware updates get delivered? Physically connecting each device to a laptop for a firmware update is acceptable for a bench test but impractical for devices mounted in enclosures or installed in awkward locations. MicroPython supports Over-The-Air (OTA) updates through community libraries that check a remote URL for a new firmware version and write it to flash without user intervention. This is worth implementing from the start for any deployment where physical access to each device for updates is more than trivially inconvenient, since retrofitting OTA update capability to an already-deployed fleet is considerably harder than building it in during initial development. Planning for it upfront adds minimal initial effort for a significant long-term operational benefit. The alternative, physical access to every device for every firmware update, scales poorly.