How to Push OTA Firmware Updates to Arduino Devices

Wirelessly updating firmware on an Arduino board over Wi-Fi

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:

  1. Flash an initial BLE OTA bootloader sketch via USB. This makes the board advertise a BLE DFU (Device Firmware Update) service.
  2. Export your compiled binary. In the Arduino IDE, go to Sketch → Export Compiled Binary. This gives you a .bin file.
  3. Transfer the .bin via 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 .bin file, 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

ProblemLikely CauseFix
Board doesn’t appear as network portFirewall, different subnet, or mDNS not runningDisable firewall temporarily; install Bonjour on Windows; confirm same network
Upload fails mid-transferSketch too large for OTA partitionCheck partition scheme; select “Minimal SPIFFS” in ESP32 board settings
Lost OTA capability after updateNew sketch didn’t include OTA codeRe-flash via USB with OTA boilerplate included
Password prompt doesn’t appearPassword not set in sketchAdd ArduinoOTA.setPassword("yourpass")
Upload succeeds but board crashesMemory overflow or watchdog timeoutCheck 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 →