Project · Advanced · ESP32
Safe OTA Updates on ESP32 (Without Bricking the Lab)
Partition schemes, rollback, and staged fleet updates for a handful of boards.
Project template
- Goal & materials
- Steps / firmware
- Troubleshooting
- AI assist notes
- Related gear & books
Over-the-air (OTA) updates save you from unplugging every board for each firmware change. They also make it possible to break every board at once. This guide covers how ESP32 OTA works, how to set up partitions and rollback so a bad build does not strand a device, and how to roll updates out to a handful of boards without drama.
How ESP32 OTA works
An OTA-capable ESP32 has two application slots in flash, usually named ota_0 and ota_1, plus a small otadata partition that records which slot to boot. The running firmware writes the new image into the inactive slot, verifies it, marks it as the next boot target, and restarts. If the new image is bad and rollback is enabled, the bootloader can return to the previous slot.
The key consequence: each slot must hold your whole firmware. On a 4 MB flash chip, the default Arduino partition scheme gives each app slot roughly 1.2 MB. If your build is close to that, pick a scheme with larger app slots (and less file system space) or use a module with more flash.
Step 1: choose the partition scheme on purpose
- Arduino IDE: Tools > Partition Scheme. Pick one that lists OTA support and fits your app size with headroom.
- PlatformIO: set
board_build.partitionsto a built-in or custom CSV. - ESP-IDF: choose a two-OTA partition table in menuconfig or supply a custom CSV.
Changing the partition table requires a wired flash, so get this right before boards go into the field. Leave room for growth; firmware only gets bigger.
Step 2: pick a delivery method
| Method | How it works | Good for |
|---|---|---|
| ArduinoOTA (push) | The IDE or espota pushes an image to the board over the LAN | Development, one board at a time |
| HTTP(S) pull | The board checks a server for a newer version and downloads it (HTTPUpdate in Arduino, esp_https_ota in ESP-IDF) | Several boards, scheduled updates |
| Web upload page | The board serves a form and accepts a .bin upload | Occasional manual updates without the IDE |
A minimal ArduinoOTA setup looks like this:
#include <WiFi.h>
#include <ArduinoOTA.h>
void setup() {
WiFi.begin("your-ssid", "your-password");
while (WiFi.status() != WL_CONNECTED) delay(250);
ArduinoOTA.setHostname("bench-node-1");
ArduinoOTA.setPassword("change-me");
ArduinoOTA.begin();
}
void loop() {
ArduinoOTA.handle();
// keep the rest of loop() non-blocking so OTA stays responsive
}
Always set a password. Without one, anyone on your network can flash the board.
Step 3: add a health check and rollback
A new image that boots but cannot reach Wi-Fi, or crashes after a minute, is the classic bricking scenario. ESP-IDF supports app rollback when CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is set: a freshly updated image starts in a "pending verify" state, and your code decides whether it is healthy.
- On boot, run a self-test: connect to Wi-Fi, reach the broker or server, read the main sensor.
- If it passes, call
esp_ota_mark_app_valid_cancel_rollback(). - If it fails, call
esp_ota_mark_app_invalid_rollback_and_reboot()to go back to the previous image. - If the new image crashes before marking itself valid, the bootloader rolls back on the next reset.
With the Arduino core, rollback support depends on the core version and on the bootloader that was built with it, so check the documentation for the version you use. If you need guaranteed rollback behavior, building with ESP-IDF (or Arduino as an ESP-IDF component) gives you full control of the bootloader configuration.
Step 4: secure the update path
- Use HTTPS with certificate validation for pull updates, not plain HTTP and not a client set to skip verification.
- Consider signed images. ESP-IDF can verify image signatures so the device only accepts firmware you signed. Secure boot goes further, but it is permanent on that chip; read the documentation fully before enabling it.
- Embed a version string and have the device report it, so you know what every board is running.
Step 5: roll out in stages
- Flash one board on the bench by OTA, not USB, so you test the real path.
- Watch it through a full cycle: reboot, reconnect, a few hours of normal work.
- Update one field board that is easy to reach.
- Then update the rest, a few at a time, confirming each reports the new version.
- Keep the previous known-good
.binso you can push it back quickly.
Common mistakes
- Firmware too large for the OTA slot. The update fails partway or will not start. Check the build size against the slot size.
- Blocking code in
loop(), soArduinoOTA.handle()rarely runs and uploads time out. - Updating battery devices on low charge. A brownout during the write wastes the attempt; check supply voltage first.
- Shipping a build that changes Wi-Fi credentials or server addresses without testing, which cuts off future OTA.
- No way back. Always keep at least one board reachable by USB and the last good image saved.
Summary
Pick a partition scheme with room to grow, use a password or HTTPS for every update path, add a self-test that marks a new image valid only when it actually works, and roll updates out one board at a time. With those pieces in place, OTA becomes routine instead of a gamble.
Related
- Projects hub
- Newsletter
- Books for deeper reading