How to Push OTA Firmware Updates to Arduino Devices

Your Arduino is zip-tied inside a weatherproof box, bolted to a fence post 12 feet up, monitoring your garden’s soil moisture. You need to change one line of code. The threshold for “dry” is wrong, and your irrigation pump kicks on 3 times a day instead of once.
You could grab a ladder, unscrew the enclosure, pull the USB cable out of a drawer, and flash the fix. Or you could type the change into your sketch and hit Upload from your couch.
That’s OTA (Over-The-Air) updating. You push new firmware to your board wirelessly, over Wi-Fi or BLE, skipping the USB cable entirely. This article walks you through the whole process: one initial USB upload to plant the OTA seed, then wireless updates from that point on. You’ll need about 20 minutes and a board with a wireless radio.
What OTA Actually Means for an Arduino
Instead of writing firmware to your board’s flash memory through a serial/USB connection, you write it through a wireless transport. Wi-Fi or BLE carries the binary. The board stores it, verifies it, and reboots into the new code.
On ESP32 boards, this works through a dual-partition scheme. The board keeps two slots for application code. It runs from one slot while writing the incoming firmware to the other. If the write succeeds, the bootloader swaps to the new slot on the next reboot. If something goes wrong, the old firmware is still sitting there intact.
ESP32 Flash Memory Layout (OTA-enabled)
+---------------------------+
| Bootloader | (selects active partition)
+---------------------------+
| OTA Partition 0 | <-- currently running
| (app0) |
+---------------------------+
| OTA Partition 1 | <-- new firmware written here
| (app1) |
+---------------------------+
| SPIFFS / NVS | (file storage / preferences)
+---------------------------+OTA on Arduino isn’t automatic like a phone update. You trigger it manually from the Arduino IDE (or a script). The board runs a small listener in the background that waits for an incoming upload, and you choose when to send one.
+-------------------+ Wi-Fi / BLE +-------------------+
| Arduino IDE | ----------------------> | Arduino Board |
| (Host PC) | firmware binary | (Running OTA |
| | | listener) |
+-------------------+ +-------------------+Which Boards Support OTA
Not every Arduino can do this. You need a wireless radio on the board itself.
Wi-Fi OTA works on:
- ESP32 / ESP8266 (via the ESP32 board package)
- Arduino Nano 33 IoT
- Arduino MKR WiFi 1010
- Arduino UNO R4 WiFi
BLE OTA works on:
- Arduino Nano 33 BLE / BLE Sense
Classic ATmega-based boards (UNO R3, Mega 2560, original Nano) don’t support OTA natively. They don’t have wireless hardware.
You’ll need:
- Arduino IDE 2.x (latest version recommended)
- The right board package installed via Board Manager
- Your PC and board on the same Wi-Fi network (for Wi-Fi OTA)
If your board has Wi-Fi, start with Section 4 below. If it only has BLE, skip to the BLE section. If it has neither, you’re stuck with USB (or you could bolt on an ESP-01 module, but that’s a different article).
Wi-Fi OTA Step-by-Step
I’ll use an ESP32 as the reference board here. If you’re on a Nano 33 IoT or MKR WiFi 1010, the process is nearly identical; I’ll flag the differences.
Step 1: Install the Board Package and Library
Open Arduino IDE. Go to Tools → Board → Boards Manager. Search for “esp32” and install the package by Espressif Systems. (For Arduino Nano 33 IoT, search for “Arduino SAMD Boards” instead.)
The ArduinoOTA library comes bundled with the ESP32 package. For official Arduino boards, you can find it via Sketch → Include Library → Library Manager, searching for “ArduinoOTA.”
Step 2: Upload the Initial OTA Sketch via USB
This is the one and only time you need the cable. Plug in your board and upload this sketch:
#include <WiFi.h>
#include <ArduinoOTA.h>
const char* ssid = "YourNetworkName";
const char* password = "YourNetworkPassword";
void setup() {
Serial.begin(115200);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected! IP: " + WiFi.localIP().toString());
// Set an OTA password (strongly recommended)
ArduinoOTA.setPassword("myOtaPass123");
// Optional: give the board a human-readable name
ArduinoOTA.setHostname("garden-sensor");
// OTA progress callbacks (helpful for debugging)
ArduinoOTA.onStart([]() {
Serial.println("OTA update starting...");
});
ArduinoOTA.onEnd([]() {
Serial.println("OTA update complete!");
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("OTA Error: %u\n", error);
});
ArduinoOTA.begin(); // Start the OTA listener
Serial.println("OTA ready.");
}
void loop() {
ArduinoOTA.handle(); // Check for incoming OTA updates
// Your normal code goes here
// e.g., read sensors, blink LEDs, etc.
}For Nano 33 IoT users: replace #include <WiFi.h> with #include <WiFiNINA.h>. Everything else stays the same.
The key pieces: ArduinoOTA.begin() in setup() starts the listener. ArduinoOTA.handle() in loop() checks for incoming uploads on every iteration. Both are required.
Upload this via USB. Open the Serial Monitor. You should see the board connect to Wi-Fi and print its IP address.
Step 3: Verify the Board Appears as a Network Port
Unplug the USB cable. In the Arduino IDE, go to Tools → Port. You should see your board listed as a network port, something like garden-sensor at 192.168.1.47.
If it doesn’t show up:
- Windows users: you might need Apple’s Bonjour service installed. The board advertises itself via mDNS, and Windows doesn’t speak mDNS without Bonjour. Download it from Apple’s support site (it’s free) and restart the IDE.
- Firewall: temporarily disable your firewall to test. OTA uses UDP port 3232 (on ESP32) and TCP for the transfer.
- Same network: your PC and the board must be on the same subnet. If you have a mesh network with client isolation enabled (a setting that stops devices on the same Wi-Fi from communicating with each other), that’ll block it.
Step 4: Modify Your Sketch and Push OTA
Make a visible change. Add a line to setup():
Serial.println("OTA v2 — updated wirelessly!");Select the network port under Tools → Port (not the USB serial port).
Hit Upload.
If you set a password, the IDE will prompt for it. Enter it. The binary compiles, transfers over Wi-Fi, and the board reboots into the new firmware. The whole process typically takes 10 to 30 seconds depending on sketch size and Wi-Fi speed.
Step 5: Confirm It Worked
If you still have a Serial connection (maybe through a second USB-to-serial adapter, or a Telnet-over-Wi-Fi setup), check the output. You should see your new "OTA v2" message.
If you don’t have serial access, verify through behavior: change an LED blink rate, change a sensor threshold, print to a web server running on the board. Pick something observable.
The Golden Rule: Always Include OTA Code
Here’s the one thing that trips people up, and it’s unforgiving.
If you upload a sketch via OTA that doesn’t contain the OTA listener code, you lose wireless update capability. The new firmware won’t have the listener running. The board will stop responding to OTA uploads. You’ll need to dig out the USB cable, reconnect physically, and re-flash.
Every sketch you ever upload must include the ArduinoOTA boilerplate. Create a template sketch that you copy as the starting point for every project, or put the OTA setup into a helper function in a separate .h file and #include it everywhere.
BLE OTA on Arduino Nano 33 BLE
If your board doesn’t have Wi-Fi but does have BLE (like the Nano 33 BLE or BLE Sense), OTA is still possible, but the workflow is different and clunkier.
The Arduino IDE doesn’t support BLE OTA through its port selection the way it does Wi-Fi. Instead, you’ll:
- Flash an initial BLE OTA bootloader sketch via USB. This makes the board advertise a BLE DFU (Device Firmware Update) service.
- Export your compiled binary. In the Arduino IDE, go to Sketch → Export Compiled Binary. This gives you a
.binfile. - Transfer the
.binvia a BLE tool. Nordic Semiconductor’s nRF Connect app on iOS or Android is the most common choice. Connect to your board’s BLE DFU service, select the.binfile, and send it.
The ArduinoBLE library handles the BLE stack on the board side. Community libraries like ArduinoBLE_OTA handle the firmware-transfer handshake for you, though they’re less mature than the Wi-Fi OTA path.
BLE OTA is slower (BLE’s throughput is limited compared to Wi-Fi), requires a phone or BLE-capable computer as the intermediary, and has less tooling support. It’s best for situations where Wi-Fi genuinely isn’t available, like a wearable or a remote sensor without network access.
Common Pitfalls and Fixes
| Problem | Likely Cause | Fix |
|---|---|---|
| Board doesn’t appear as network port | Firewall, different subnet, or mDNS not running | Disable firewall temporarily; install Bonjour on Windows; confirm same network |
| Upload fails mid-transfer | Sketch too large for OTA partition | Check partition scheme; select “Minimal SPIFFS” in ESP32 board settings |
| Lost OTA capability after update | New sketch didn’t include OTA code | Re-flash via USB with OTA boilerplate included |
| Password prompt doesn’t appear | Password not set in sketch | Add ArduinoOTA.setPassword("yourpass") |
| Upload succeeds but board crashes | Memory overflow or watchdog timeout | Check free heap with ESP.getFreeHeap(); add yield() in long loops |
Keep It Locked Down
OTA without a password means anyone on your Wi-Fi network can flash your board. On your home network, that’s probably just your family. On a shared network (hackerspace, dorm, office), it’s everyone.
Always set a password with ArduinoOTA.setPassword(). For home hobby projects, that’s enough.
For commercial or scaled deployments, you’d want signed firmware images, encrypted transport, and a secure bootloader. That’s beyond what the Arduino IDE provides natively, but frameworks like ESP-IDF offer it. If you’re moving toward production IoT devices, the Hubble device security documentation covers how firmware signing and secure boot work in connected device deployments.
Tools Worth Exploring Beyond the Basics
PlatformIO gives you OTA uploads with scripting support and CI/CD integration. If you’re managing more than 2 or 3 boards, the command-line workflow is faster than the Arduino IDE.
ESP-IDF (Espressif’s native framework) offers advanced OTA with automatic rollback, encrypted transfers, and dual-app partition management. It’s more complex but much more capable.
Arduino IoT Cloud and Blynk are managed platforms that let the cloud push updates to registered devices. You upload once to the platform, and it distributes to your fleet.
Your First OTA Update, Today
Here’s the path: pick the simplest sketch you have running on a Wi-Fi-capable board. Add the OTA boilerplate from Step 2 above. Upload it via USB one final time. Then unplug the cable, make a small change, and push it over Wi-Fi.
The first time takes about 15 minutes. After that, every new sketch just starts from your OTA template, and you won’t think twice about it. Especially useful when your Arduino is zip-tied to a fence post 12 feet in the air.
Hubble Network enables OTA updates to devices anywhere on Earth via Bluetooth-to-satellite connectivity—no Wi-Fi, no cellular, no line of sight required. See how it works →