OTA Firmware Updates

Over-the-air update is the single most important capability in an IoT product, and it's worth stating why in blunt terms: every other design flaw is fixable if you can push new firmware, and none of them are if you can't. A security vulnerability without OTA is a recall. A protocol bug without OTA is a truck roll to every installation. Teams that treat OTA as a later milestone routinely discover this at the worst possible time.

The engineering problem is that an update must be atomic and recoverable on a device that can lose power mid-write, lose connectivity mid-download, and has nobody nearby to intervene. The standard solution is a dual-slot layout: write the new image to an inactive partition, verify its signature and integrity, flip a pointer, boot it, and require the new firmware to confirm itself — reverting automatically if it doesn't. Everything else is refinement.

TL;DR

The Update Flow

The cost is flash: you need room for two full images plus the bootloader. That's the main reason single-slot schemes persist on very constrained parts, and it's usually the wrong economy — a slightly larger flash part is far cheaper than a bricked fleet.

Quick Example

An ESP-IDF OTA update with signature verification and explicit confirmation:

The PENDING_VERIFY block is the part people skip, and it's what turns "we can push firmware" into "we can push firmware safely." Without it, an image that boots but can't connect to the network is unrecoverable.

Core Concepts

Signing and verification

⚠️ Warning: An update mechanism that accepts unsigned images is a remote code execution vulnerability across your entire fleet. Anyone who can spoof the update server, or who compromises it, owns every device. Signature verification must happen on the device, not on the server.

The private key lives in an HSM or a signing service, never on a build machine or in CI as a plain secret. The public key is fused into the device at manufacture, ideally in one-time-programmable memory so it can't be replaced. ECDSA P-256 or Ed25519 are the usual choices; RSA-2048 appears in older designs.

Secure boot extends this to the whole chain: the ROM bootloader verifies the second-stage bootloader, which verifies the application. Without it, an attacker with physical access replaces the bootloader and your application signature check never runs.

Anti-rollback

Signature verification proves an image is yours; it doesn't prove it's current. An attacker can replay a legitimately signed older image that contains a since-patched vulnerability.

The fix is a monotonic security version stored in one-time-programmable fuses. The bootloader refuses any image whose version is below the fuse value, and the value is burned only after an update is confirmed healthy. Note the irreversibility: once burned, that device can never run an older image, so a bad release with an incremented security version is unrecoverable. Increment the security version only when a release fixes a vulnerability that must not be reachable again.

Confirm-or-rollback

The self-test must include whatever is required to receive the next update — typically network connectivity and a successful server handshake. Firmware that boots fine but can't reach the update server is functionally bricked, and a self-test that only checks local peripherals won't catch it.

Delta updates

Sending only the difference between versions:

On cellular fleets this is the difference between an affordable update and one nobody approves. The costs: the server must generate and store a patch per source version, the device needs scratch space and patching code, and a device that's several versions behind needs either a chain of patches or a full image. Most fleets implement delta with a full-image fallback.

Transport

The gateway pattern matters for large local deployments: one download over the WAN serving hundreds of nodes is a substantial saving over each node fetching independently.

Best Practices

Build and test the OTA path first

Before any product feature. A device that can't be updated is a device whose every subsequent bug is permanent. This should be working in the first prototype, not the last sprint.

Test failure paths, not just the happy path

Deliberately cut power during download, during the pointer write, and during first boot. Deliver a corrupted image, a truncated one, an image signed with the wrong key, and an image that boots and immediately hangs. These are the scenarios that determine whether a fleet survives a bad release, and they're the ones that never get tested by accident.

Gate updates on device conditions

Check battery level, available flash, and connection stability before starting. An update that begins on a device at 15% battery and dies mid-write is the archetypal brick — and it's entirely preventable with one conditional.

Stage every rollout

Automate the halt: if the error rate or the check-in rate regresses beyond a threshold, stop the rollout without waiting for a human. The staged rollout is what converts "we shipped a bad release" from a fleet-wide incident into a 1% incident.

Make devices report their state

The server should know each device's firmware version, update status, last check-in, and last error. Without that telemetry you cannot tell whether a rollout is succeeding, which devices are stuck, or whether a rollback is happening quietly across the fleet.

Handle version skew permanently

At any moment your fleet runs several firmware versions, and some devices will be years behind after sitting in a warehouse. Server APIs and message schemas must remain backward compatible, messages must carry a schema version, and the update server must know how to bring a very old device forward — possibly through an intermediate version.

Keep the bootloader immutable if you can

Updating the bootloader is the one operation with no recovery path — a failure there bricks the device permanently. Where the hardware allows, write-protect it and put all updateable logic in the application. If a bootloader update is truly necessary, use a two-stage scheme where a minimal immutable first stage can always recover.

Pair the watchdog with the update system

The watchdog is what catches firmware that boots and then hangs, which signature verification cannot detect. Ensure a watchdog reset during the pending-verify window triggers a rollback rather than an endless reboot loop.

Common Mistakes

Accepting unsigned images

Overwriting the running firmware

Switching without a confirmation step

A self-test that doesn't test connectivity

Updating without checking preconditions

Shipping to the whole fleet at once

That last parenthetical is the scenario that ends companies: a bug in the network stack means the devices can't check in, so they can never be told about the fix.

FAQ

How much flash do I need?

Roughly twice the application size plus the bootloader and data partitions. A 1 MB application wants 4 MB of flash comfortably. When flash is genuinely constrained, options are a compressed image with decompression during the swap, delta updates, or an external flash chip for staging — but choosing a larger part at design time is nearly always cheaper than engineering around it.

What if the device is offline for a year?

It must still update from whatever version it's on. Keep old images and their delta chains available, allow multi-step upgrades, and never assume a device is at most one version behind. Warehouse stock routinely ships years after manufacture, and those devices need a working path to current.

Should I use an OTA framework or build my own?

Use an existing one. ESP-IDF has OTA built in; MCUboot is the widely used secure bootloader across Zephyr, nRF, and many others; SWUpdate and RAUC serve embedded Linux; Mender and balena are full device-management platforms. These implement the atomicity, verification, and rollback logic that's easy to get subtly wrong and catastrophic to get wrong at scale.

How do I handle a bad release that's already deployed?

If devices still check in, halt the rollout and push a fix — this is why staged rollout exists. If the release broke connectivity, your options are whatever recovery mechanism you built in advance: an automatic rollback on repeated failed check-ins, a fallback to a known-good factory image after N failed boots, or physical intervention. Build the first two; the third is a business event.

Can I update a bootloader over the air?

Technically yes, and it's the highest-risk operation in embedded systems — a failure has no recovery path. Where possible, make the bootloader immutable and put everything updateable in the application. If it must be updateable, use a minimal immutable first stage that can always recover, and treat the update itself with far more caution than an application update.

What about embedded Linux devices?

The same principles with different tooling: A/B root filesystem partitions, verified boot with dm-verity, and atomic switching via the bootloader environment. RAUC, SWUpdate, Mender, and OSTree implement it. The scale is larger — hundreds of megabytes rather than one — which makes delta updates and bandwidth planning more important, not less.

Related Topics

References