Maker tech hub — AI · 3D print · Pi · ESP32 · plus the classic tech archive.

Free ESP32 kit · AI tools directory · Books · Archive

Project · Advanced · ESP32

Safe OTA Updates on ESP32 (Without Bricking the Lab)

Partition schemes, rollback, and staged fleet updates for a handful of boards.

Written by

Avery J. Parker

IT veteran, maker educator, and author of Network Ninja, 3D Printing Mastery, and AI Workflow Mastery. Business IT: Diversified Tech Solutions.

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.partitions to 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

MethodHow it worksGood for
ArduinoOTA (push)The IDE or espota pushes an image to the board over the LANDevelopment, one board at a time
HTTP(S) pullThe 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 pageThe board serves a form and accepts a .bin uploadOccasional 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.

  1. On boot, run a self-test: connect to Wi-Fi, reach the broker or server, read the main sensor.
  2. If it passes, call esp_ota_mark_app_valid_cancel_rollback().
  3. If it fails, call esp_ota_mark_app_invalid_rollback_and_reboot() to go back to the previous image.
  4. 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

  1. Flash one board on the bench by OTA, not USB, so you test the real path.
  2. Watch it through a full cycle: reboot, reconnect, a few hours of normal work.
  3. Update one field board that is easy to reach.
  4. Then update the rest, a few at a time, confirming each reports the new version.
  5. Keep the previous known-good .bin so 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(), so ArduinoOTA.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