An ESP8266 mounted inside a wall box, hidden inside a lamp base, or bolted to the top of a fence post has one thing in common with every other deployed project: getting a USB cable back to it for a firmware update is a genuine hassle. OTA (Over-The-Air) updates solve exactly that -- pushing new firmware to the board over WiFi, with no cable required after the very first flash.
Two Different OTA Approaches
| Approach | Best for | How it works |
|---|---|---|
| ArduinoOTA | Active development on your own network | Board appears as a Network Port in the Arduino IDE; upload like a normal USB flash, just over WiFi |
| Web-based update (HTTPUpdateServer) | Field-deployed devices, or updating from any browser without the IDE | Board hosts a simple upload page; drag a compiled .bin file in |
ArduinoOTA is the better fit while a project is still being actively developed on the same local network as your computer. The web-based approach is more practical once a device has shipped somewhere you won't have your dev machine handy, or when someone other than the original developer needs to push an update.
Setting Up ArduinoOTA
#include <ESP8266WiFi.h>
#include <ArduinoOTA.h>
const char* ssid = "YourWiFiName";
const char* password = "YourWiFiPassword";
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
ArduinoOTA.setHostname("esp8266-project");
ArduinoOTA.setPassword("choose-a-real-password");
ArduinoOTA.onStart([]() { Serial.println("OTA starting"); });
ArduinoOTA.onEnd([]() { Serial.println("OTA complete"); });
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("OTA error: %u\n", error);
});
ArduinoOTA.begin();
Serial.println("Ready. IP: " + WiFi.localIP().toString());
}
void loop() {
ArduinoOTA.handle(); // must run every loop iteration, unblocked
// ... rest of your project code
}
Once this is flashed once over USB, the board shows up under Tools > Port as a network port in the Arduino IDE from then on -- selecting it and hitting upload sends the new sketch over WiFi instead of a cable. A genuinely robust WiFi connection matters more here than in most sketches, since a flaky connection can drop mid-upload -- see Connecting ESP8266 NodeMCU to WiFi for a reconnect loop worth pairing with any OTA-enabled project.
Setting Up a Web-Based Update Page
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h>
const char* ssid = "YourWiFiName";
const char* password = "YourWiFiPassword";
ESP8266WebServer server(80);
ESP8266HTTPUpdateServer httpUpdater;
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
httpUpdater.setup(&server, "/update", "admin", "choose-a-real-password");
server.begin();
Serial.println("Update page at: http://" + WiFi.localIP().toString() + "/update");
}
void loop() {
server.handleClient();
}
Visiting /update on the board's IP address shows a basic upload form -- choosing a compiled .bin file (exported from the Arduino IDE via Sketch > Export Compiled Binary) and submitting it flashes the new firmware directly from any browser, no IDE required on the updating machine at all.
Flash Size and Why Uploads Sometimes Fail
OTA needs enough free flash to hold both the currently running firmware and the incoming update simultaneously during the flash process. The board's flash size and partition split (set under Tools > Flash Size in the Arduino IDE) determines how much room is actually available for this -- a sketch that's grown close to the board's total flash capacity can fail an OTA upload with a space error, even though the exact same sketch flashed fine over USB the first time, since USB flashing doesn't need to keep both copies around at once.
Common Mistakes
Not leaving enough flash headroom for OTA. A sketch needs roughly double its own size available during the update -- check Tools > Flash Size if uploads that used to work start failing as a project grows.
Leaving the default or no OTA password. Anyone else on the same network can push firmware to an unprotected OTA endpoint -- always set a real password with setPassword() or in the web updater setup.
Blocking loop() with delay(). ArduinoOTA.handle() needs to run frequently to catch an incoming update -- a sketch full of long blocking delays can miss update requests or fail mid-transfer.
Forgetting to call the handler at all. ArduinoOTA.handle() (or server.handleClient() for the web approach) has to execute every loop() iteration -- omitting it silently disables OTA entirely, without any error.
Losing power mid-update. A power cut while new firmware is being written is a genuine bricking risk on a plain ESP8266 -- there's no automatic fallback to the previous firmware if the write is interrupted, so OTA updates are best performed with a stable power source, not right as a device is about to lose power.
Where to Go Next
OTA is only as reliable as the WiFi connection underneath it -- Connecting ESP8266 NodeMCU to WiFi: A Robust, Non-Blocking Reconnect Loop is the natural companion piece for any device that needs to stay reachable for updates over the long term, not just on the first flash.