ESP32 OTA Updates: Wireless Firmware Flashing
Why OTA Matters
Once an ESP32 is deployed somewhere inconvenient (inside a wall, mounted outdoors, buried in a project enclosure), physically retrieving it for every firmware update via USB gets old fast. OTA (Over-The-Air) updates let you push new firmware over WiFi instead.
Partition Scheme Requirement
OTA needs a partition scheme with two app slots (so the device can download new firmware to the inactive slot while still running the current one, then switch) — in Arduino IDE, Tools > Partition Scheme, pick anything with "OTA" in the name (e.g. "Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS)"). If you previously selected a no-OTA partition scheme (like the "Huge APP" scheme mentioned in the ESP32-CAM setup guide, which sacrifices OTA capability for more single-slot app space), you'll need to reflash with an OTA-compatible scheme via USB one more time before OTA becomes possible at all.
Basic Arduino OTA (Simplest Path)
#include <ArduinoOTA.h> void setup() { WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) delay(500); ArduinoOTA.setHostname("my-esp32-device"); ArduinoOTA.begin(); } void loop() { ArduinoOTA.handle(); // must be called regularly // ... rest of your normal loop code ... }With this running, your device appears as a network port option directly in Arduino IDE's Tools > Port menu (alongside physical COM ports) — select it and Upload works exactly like a USB upload, just wirelessly.
Web-Based OTA (No IDE Required)
For updating devices without needing Arduino IDE open (useful for less technical household members, or devices you want to update from a phone), the Update library with a simple web upload form works well:
#include <WebServer.h> #include <Update.h> WebServer server(80); void setup() { // ... wifi connect ... server.on("/update", HTTP_POST, []() { server.send(200, "text/plain", Update.hasError() ? "FAIL" : "OK"); ESP.restart(); }, []() { HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { Update.begin(UPDATE_SIZE_UNKNOWN); } else if (upload.status == UPLOAD_FILE_WRITE) { Update.write(upload.buf, upload.currentSize); } else if (upload.status == UPLOAD_FILE_END) { Update.end(true); } }); server.begin(); }Navigate to http://<esp32-ip>/update, upload a compiled .bin file (Sketch > Export Compiled Binary in Arduino IDE generates this) through a simple browser form.
Security Consideration
Both approaches above accept OTA updates from anyone who can reach the device's network — for anything beyond a closed home LAN, add authentication (ArduinoOTA.setPassword() for the IDE method, or basic auth on the web upload endpoint) so a compromised network segment can't push arbitrary firmware to your devices.
Recovering From a Bad OTA
If a pushed update is broken/bricks the device's normal operation, you generally still need physical USB access to recover — OTA's dual-partition safety net helps against a failed/interrupted transfer (it won't switch to a corrupted partition), but doesn't protect against successfully flashing genuinely broken application logic. Test updates on a bench unit before pushing to a hard-to-reach deployed device.
Related Guides
- ESP32 Secure Boot and Flash Encryption: Protecting Firmware and Data on Deployed Devices
- How to Program Addressable LED Strips: WS2812B Patterns, Effects, and Power Design
- I2C vs SPI vs UART: How to Choose and Use Serial Communication Protocols
- How to Use Sensors with Arduino and ESP32: Temperature, Distance, Load, Current, and Hall Effect
- How to Control Motors with Arduino and ESP32: Stepper, DC, and Servo Drivers
- How to Hack Wi-Fi and Bluetooth with the Flipper Zero and Wi-Fi Marauder
- ESP32: Setting Up for Arduino IDE
- Setting Up Marauder on the ESP32 Wi-Fi Dev Board for Flipper