Electronics Design AU
Firmware

How Does an OTA Firmware Update Work?

Last updated 29 June 2026 · 10 min read

Direct Answer

An OTA (over-the-air) firmware update delivers a new firmware image wirelessly — over Wi-Fi, BLE, cellular, or any available network interface — and installs it without physical access to the device. A safe OTA update requires: (1) writing the new image to a secondary flash partition while the current firmware continues running; (2) verifying the new image's integrity and authenticity (CRC, hash, or cryptographic signature) before committing; (3) a bootloader that can switch the active partition on next boot; and (4) a confirmation step after the new firmware boots successfully, preventing a bricked device if the new image has a boot fault. If power is lost at any point before confirmation, the bootloader falls back to the known-good image.

Detailed Explanation

A firmware update delivered over a wireless or network interface seems straightforward — send the new binary to the device, write it, reboot. The complexity arises from a constraint unique to embedded systems: if anything goes wrong (power loss, network drop, image corruption, boot fault in the new firmware), the device must still boot into a working state. A product that is bricked by a failed update is expensive to recover — it must be physically retrieved, connected to a programmer, and reflashed, or written off entirely.

Safe OTA update design resolves this by ensuring the existing firmware remains intact and bootable until the new firmware has successfully started and confirmed itself.

The A/B Partition Scheme

The foundational pattern for OTA-safe firmware update is dual partition (A/B) storage, also called the primary/secondary slot model:

Flash layout:
┌──────────────────┐  0x08000000 (STM32) or 0x1000 (ESP32 partition table start)
│   Bootloader     │  Protected, never overwritten by OTA
├──────────────────┤
│   Primary slot   │  Running firmware (A)
│   (Slot 0)       │  MCU executes from here
├──────────────────┤
│   Secondary slot │  New firmware staged here (B)
│   (Slot 1)       │  Written during OTA; not yet active
├──────────────────┤
│   Scratch/other  │  (MCUboot swap scratch area, or NVS, or factory data)
└──────────────────┘

The update sequence:

  1. Receive — the running application receives the new firmware image (in chunks over network) and writes it to the secondary slot. The primary slot continues to run throughout.
  2. Verify — after the full image is written, verify its integrity: CRC check at minimum; cryptographic signature verification for security-critical products.
  3. Request swap — write a flag in non-volatile storage (a scratch area or dedicated NVS sector) indicating the bootloader should activate the secondary slot on next boot.
  4. Reboot — the application reboots into the bootloader.
  5. Swap — the bootloader reads the swap request, validates the secondary image, and either swaps the contents of both slots (MCUboot swap mode) or changes the boot pointer to the secondary slot (direct-xip mode).
  6. Test mode — the new firmware boots in a "pending confirmation" state. The bootloader marks the image as not yet confirmed.
  7. Confirm — the new firmware runs its post-boot health checks (peripheral self-test, comms connectivity, watchdog serviceable, etc.) and calls the OTA confirm API. If this is not called within a configurable number of boot attempts, the bootloader reverts to the primary slot on the next reset.

MCUboot

MCUboot is the open-source bootloader that implements this swap mechanism. It is the default bootloader for Zephyr RTOS, Mynewt, and is supported in ESP-IDF as an alternative to the native ESP bootloader. It also integrates with STM32 projects via the STM32 MCUboot integration.

MCUboot's image format prepends a 32-byte header to every firmware binary:

struct image_header {
    uint32_t  ih_magic;        /* 0x96f3b83d — MCUboot magic number */
    uint32_t  ih_load_addr;    /* 0 for Cortex-M slot-based images */
    uint16_t  ih_hdr_size;     /* size of this header (typically 32 bytes) */
    uint16_t  ih_protect_tlv_size;
    uint32_t  ih_img_size;     /* payload size in bytes */
    uint32_t  ih_flags;        /* IMAGE_F_PIC, IMAGE_F_ENCRYPTED, etc. */
    uint8_t   ih_ver.major;    /* image version */
    uint8_t   ih_ver.minor;
    uint16_t  ih_ver.revision;
    uint32_t  ih_ver.build_num;
    uint32_t  _pad1;
};

After the header, MCUboot appends a TLV (Type-Length-Value) trailer containing the image hash (SHA-256 or SHA-384), and optionally an RSA-2048 or EC-256 signature. The imgtool utility (distributed with MCUboot) adds this header and signs the binary:

imgtool sign \
  --key signing_key.pem \
  --header-size 0x20 \
  --align 4 \
  --version 1.2.0 \
  --slot-size 0x60000 \
  firmware.bin \
  firmware_signed.bin

The bootloader verifies the signature using the corresponding public key compiled into the bootloader binary. An image that does not pass signature verification is never booted, preventing execution of corrupt or injected images.

MCUboot swap modes:

  • swap-move — shuffles sectors between slots during the swap. Allows reverting to the old image because the old image is preserved in the secondary slot until the new image is confirmed. Requires a scratch area for one sector of temporary storage.
  • swap-scratch — classic swap using a scratch sector. Slower but requires less overhead.
  • direct-xip — no swap; the bootloader simply executes from whichever slot has the highest confirmed version. Faster, but the application binary must be compiled for both possible load addresses (position-independent code or separate builds). Old image is lost once new image is confirmed.
  • ram-load — copies the image from flash to RAM and executes from RAM. Used on MCUs with XIP-capable external flash where RAM is faster.

OTA on ESP32

The ESP-IDF provides a first-class OTA implementation built into the SDK. The default ESP32 partition table reserves two OTA data partitions (ota_0 and ota_1) and a small otadata partition that records which OTA slot to boot from:

Default OTA partition table (Name, Type, SubType, Offset, Size):

nvs,      data, nvs,     0x9000,  0x5000,
otadata,  data, ota,     0xe000,  0x2000,
app0,     app,  ota_0,   0x10000, 0x140000,
app1,     app,  ota_1,   0x150000,0x140000,

The application receives the update and writes it using the esp_ota API:

esp_ota_handle_t ota_handle;
const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);

esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &ota_handle);

/* Write chunks as they arrive (e.g. from HTTPS download) */
while (bytes_remaining > 0) {
    size_t chunk_size = receive_chunk(buffer, sizeof(buffer));
    esp_ota_write(ota_handle, buffer, chunk_size);
    bytes_remaining -= chunk_size;
}

esp_ota_end(ota_handle);                         /* Finalise and verify */
esp_ota_set_boot_partition(update_partition);    /* Request boot from new partition */
esp_restart();                                   /* Reboot into new firmware */

After restart, the new firmware calls esp_ota_mark_app_valid_cancel_rollback() to confirm itself. If the new firmware crashes before calling this, the ESP-IDF bootloader automatically reverts to the previous OTA slot on the next boot (configurable via CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE).

For HTTPS delivery (strongly recommended over plain HTTP to prevent image injection), ESP-IDF includes esp_https_ota() which handles certificate verification, chunked download, and write in a single call.

OTA on STM32

STM32 OTA implementation options depend on the device family:

STM32H7 and STM32L5 with native dual-bank flash: These MCUs support hardware bank swap — the application writes the new image to the inactive bank, then sets a flag in the flash option bytes. On the next reset, the bootloader swaps which bank is visible at 0x08000000. No sector-by-sector copy is needed; the swap is instantaneous. The STM32H7 reference manual (RM0433) covers dual-bank operation in the Flash Interface chapter.

STM32F4 and STM32F7 (single-bank, manual A/B layout): These require the application to manually implement A/B partition management in a custom bootloader. MCUboot on STM32 is the standard approach — ST provides integration guides and reference implementations for STM32F4 and STM32U5 in their GitHub repositories. The linker script partitions flash into bootloader, primary slot, and secondary slot regions — each assigned an ORIGIN address within the MCU's flash address space.

STM32 SBSFU (Secure Boot and Secure Firmware Update): An ST-provided reference bootloader that adds secure element key storage, binary encryption, and rollback counting on top of the dual-slot model. Suitable for applications targeting IEC 62443 or PSA Certified security levels.

Image Verification

At minimum, the receiving firmware must verify the downloaded image before activating it:

/* CRC-32 check (minimum — does not protect against tampering) */
uint32_t crc = crc32_compute(image_buffer, image_length);
if (crc != image_header.expected_crc) {
    ota_abort();    /* Image corrupted — do not activate */
}

For security-conscious designs:

  • SHA-256 hash: verifies integrity against accidental corruption.
  • RSA-2048 or EC-256 signature: verifies the image was signed by the legitimate manufacturer. Prevents loading unsigned or modified images even if an attacker can intercept the delivery channel.
  • Version check: reject downgrades to prevent rollback attacks where an attacker forces installation of an older image with known vulnerabilities. MCUboot's --security-counter flag and the ih_ver field enable monotonic version enforcement.

For secure OTA integration on commercial embedded products, Zeus Design's firmware team designs and implements OTA systems including MCUboot integration, signing infrastructure, and HTTPS delivery for STM32 and ESP32 platforms.

Rollback and Confirmation

The confirmation step is what distinguishes a resilient OTA from a dangerous one. The new firmware must:

  1. Boot successfully (no hard fault, no crash in initialisation).
  2. Establish connectivity (for network-connected devices, verify the network interface is working — otherwise the next update cannot be delivered).
  3. Pass any application-level self-tests.
  4. Call the confirm API (esp_ota_mark_app_valid_cancel_rollback() on ESP32, boot_set_confirmed() in MCUboot).

If confirmation does not occur (crash, reboot, watchdog reset before the confirm call), the bootloader decrements a trial count. After the configured number of failed trial boots, the bootloader reverts to the last confirmed image. The product recovers to the previous firmware version automatically.

Design Considerations

  • Never overwrite the running firmware in place. This is the single most important OTA rule. If power is lost mid-write and only one partition exists, the device has no bootable image. A/B partitioning means the running partition is never touched during the update.
  • Store the secondary partition in the same flash chip as the primary. External QSPI flash can act as the secondary slot, but the bootloader must initialise the QSPI interface before it can read or execute from it — adding complexity and failure modes. For simpler systems, keep both slots in internal flash even if it reduces image size.
  • Transport security is separate from image security. Delivering the image over HTTPS (TLS) protects the download channel. Signing the image protects against modification in storage. Both are needed: a device that trusts TLS implicitly but executes any image received over TLS is still vulnerable to a compromised update server.
  • Plan for failed delivery during deployment. A firmware campaign that deploys to 10,000 devices over 24 hours will have some devices that lose power or connectivity mid-download. The update system must be resumable, idempotent (re-downloading and re-writing the same image has no side effects), and not leave the device in a half-updated state.

Common Mistakes

  • Calling the OTA confirm immediately after esp_restart() (or equivalent) rather than after the new firmware has verified its own health. If the new firmware has a boot-time crash that doesn't manifest until 30 seconds after startup, confirming at boot second zero means rollback never triggers. Delay the confirm call until after all startup self-checks pass.
  • Ignoring the rollback trial count. If CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is set but the trial count is 1, the first failed boot attempt triggers rollback — which may be overly aggressive if the new firmware has a timing-sensitive startup that occasionally takes slightly longer than the watchdog allows. Calibrate the trial count and watchdog timeout together.
  • Shipping with a self-signed certificate pinned for OTA delivery. Pinning a self-signed certificate saves setup time but means that when the certificate expires or the key is rotated, every deployed device loses the ability to update until new firmware is pushed — which requires the same broken OTA channel to receive. Use a certificate authority chain that allows key rotation without firmware update.
  • Not validating the version before activating. Accepting any valid signature without a version check allows downgrade attacks. Include minimum version enforcement in the bootloader and reject images older than the current confirmed version.
  • Not testing OTA with power interruption during write. The most important OTA test is not "does a clean update succeed" but "does a power-cycled mid-write device still boot the old firmware." Run this test during development, not after deployment.

Frequently Asked Questions

What happens if power is lost during an OTA update?
With a proper A/B partition scheme, power loss at any point during the update is safe. The new image is written to the secondary partition while the primary continues to hold the running firmware. Power can be lost at any byte of the write process and the bootloader still boots the primary partition on the next power cycle — the secondary partition is only activated after (1) the full image has been written, (2) image verification passes, and (3) the bootloader is instructed to swap boot slots. Even after the swap, the bootloader does not erase the old image until the new firmware has confirmed itself as working. If the new firmware crashes before confirming, the bootloader reverts to the previous image on the next boot.
Can I perform an OTA update without dual-bank flash?
Only in limited scenarios. The standard approach — write to inactive partition while running from active partition — requires flash space for two images. Some MCUboot configurations support direct-xip mode (execute-in-place from the secondary slot without swap) which reduces flash overhead, and a compressed or delta-update approach can reduce the secondary slot size. For microcontrollers with very limited flash (under 256 KB), a deferred-update model where a small recovery bootloader in protected flash downloads and writes the image at startup is an alternative, but requires the bootloader to be network-capable. On ESP32, the OTA partition table already reserves two slots in the default configuration.
What is the difference between OTA and DFU?
DFU (Device Firmware Update) typically refers to wired reprogramming — most commonly the USB DFU class, where a host computer pushes firmware to a USB-connected device via a standardised protocol. OTA (over-the-air) refers specifically to wireless delivery. Both require a bootloader to receive, store, verify, and activate the new image. The delivery transport (USB vs. network) is different; the bootloader-side logic for partition management, verification, and swap is structurally identical. A product can support both: USB DFU for factory programming and initial field updates, OTA over Wi-Fi or BLE for end-user updates.

References

Related Questions

Related Forum Discussions