Back to Blog
Embedded Systems

IoT Bootloader Design: Secure Boot, A/B Partitions, and Reliable OTA Recovery

The bootloader is the first code that runs on your IoT device and the last line of defense against bricked devices and firmware tampering. Here is how to design a production bootloader with secure boot, flash encryption, A/B OTA, and anti-rollback protection.

June 25, 2024
14 min read
BootloaderSecure BootOTAESP32

IoT Bootloader Design: Secure Boot, A/B Partitions, and Reliable OTA Recovery

Every IoT device that ships to a customer carries an implicit guarantee: it will keep working, and it will not be hijacked. The bootloader is responsible for both. It validates firmware integrity before execution and manages the OTA update lifecycle that keeps devices current without bricking them.

A poorly designed bootloader — or no bootloader security at all — is a vulnerability that attackers can exploit to run arbitrary code on your device, and an operational risk that turns a failed OTA update into a permanently bricked unit.

Bootloader Responsibilities

A production bootloader does three things:

  • 1. Hardware initialization: Clock setup, memory controller configuration, essential peripheral init
  • 2. Firmware validation: Verify that the firmware to be loaded has not been tampered with and is authorized
  • 3. Boot selection: Decide which firmware image to load (factory, OTA slot A, OTA slot B) and handle rollback
  • On ESP32, the first-stage bootloader is immutable ROM code. The second-stage bootloader (which you can configure and extend) handles validation and boot selection.

    ESP32 Secure Boot V2: RSA-PSS Signature Verification

    Secure Boot V2, available on ESP32-S2, S3, C3, and C6, uses RSA-3072 with PSS padding to sign firmware binaries. The public key hash is burned into eFuses — one-time programmable bits that cannot be changed after provisioning.

    Generating signing keys:

    Generate RSA-3072 private key (keep this SECRET — offline, HSM ideally)

    espsecure.py generate_signing_key --version 2 secure_boot_signing_key.pem

    Extract and display public key hash (burn this to eFuse)

    espsecure.py digest_sbv2_public_key --keyfile secure_boot_signing_key.pem

    Signing a firmware binary:

    Sign during build (automated in CI/CD)

    espsecure.py sign_data --version 2 --keyfile secure_boot_signing_key.pem --output firmware_signed.bin firmware.bin

    Enable in sdkconfig:

    CONFIG_SECURE_BOOT=y
    CONFIG_SECURE_BOOT_V2_ENABLED=y
    CONFIG_SECURE_BOOT_SIGNING_KEY="secure_boot_signing_key.pem"
    CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=y
    

    Once enabled and eFuses burned, the bootloader verifies the RSA-PSS signature of every firmware image before loading it. An unsigned or tampered binary is rejected — the device will not boot it under any circumstances.

    Critical operational note: Burn secure boot eFuses in a controlled manufacturing environment. Once burned, they cannot be undone. Test extensively with a non-fused development device before production.

    Flash Encryption: Protecting Firmware at Rest

    Secure boot prevents unsigned firmware from running. Flash encryption prevents firmware from being read off the flash chip via SPI bus sniffing or physical chip extraction — important for IP protection.

    CONFIG_FLASH_ENCRYPTION_ENABLED=y
    CONFIG_FLASH_ENCRYPTION_MODE_RELEASE=y  # Development mode allows plaintext; release does not
    

    In release mode, flash encryption uses AES-256-XTS. The encryption key is generated on-device during first boot and stored in eFuse. It never leaves the chip. OTA updates are transmitted in plaintext over TLS (encrypted in transit) and written to flash, where they are transparently encrypted by the hardware.

    Important: Once flash encryption is enabled in release mode, you cannot use esptool.py to reflash the device with a plain binary. All production firmware updates must go through OTA. Plan your manufacturing test and failure recovery process accordingly.

    A/B OTA Partition Scheme

    The A/B partition scheme is the backbone of reliable OTA updates. Two application partitions exist (ota_0 and ota_1). The otadata partition tracks which slot is active and which is pending.

    Partition table: see our partition table guide for full details

    ota_0, app, ota_0, 0x110000, 1M, ota_1, app, ota_1, 0x210000, 1M, otadata, data, ota, 0xf000, 0x2000,

    OTA update flow:

  • 1. Device is running from ota_0 (active)
  • 2. OTA server pushes new firmware
  • 3. Firmware writes to ota_1 (inactive slot)
  • 4. On completion, otadata is updated to mark ota_1 as pending
  • 5. Device reboots
  • 6. Bootloader reads otadata, loads ota_1
  • 7. New firmware runs and calls esp_ota_mark_app_valid_cancel_rollback()
  • 8. otadata marks ota_1 as confirmed active
  • If step 7 never happens (new firmware crashes on boot), the bootloader automatically rolls back to ota_0 on next boot.

    #include "esp_ota_ops.h"
    #include "esp_https_ota.h"

    void confirm_running_firmware(void) { // Call this early in app_main after basic sanity checks pass esp_err_t err = esp_ota_mark_app_valid_cancel_rollback(); if (err != ESP_OK) { ESP_LOGE("OTA", "Failed to mark firmware valid: %s", esp_err_to_name(err)); // This should never happen — log and continue } ESP_LOGI("OTA", "Firmware confirmed valid — rollback cancelled"); }

    void app_main(void) { // Initialize hardware hardware_init();

    // Confirm firmware is working (do this BEFORE connecting to WiFi) confirm_running_firmware();

    // Now start normal operation wifi_connect(); mqtt_start(); start_sensor_tasks(); }

    Critical timing: Call esp_ota_mark_app_valid_cancel_rollback() only after confirming basic functionality — hardware init, watchdog setup. Do not call it before you are sure the device is working. If your application crashes before calling this, the bootloader will roll back to the previous firmware on the next boot, which is exactly the behavior you want.

    Anti-Rollback Version Counter

    Rollback protection prevents a downgrade attack: an attacker intercepts OTA traffic and replaces a new, patched firmware with an old, vulnerable version. The ESP32 eFuse contains a 32-bit rollback counter. Each bit represents one increment, and bits can only be set (never cleared).

    // In your firmware, set the security version
    // This is burned into eFuse during OTA confirmation if the new version is higher
    CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK=y
    CONFIG_BOOTLOADER_APP_SEC_VER=3  // Current security version
    

    When the bootloader loads firmware, it checks the firmware's declared security version against the eFuse counter. If the firmware version is lower than the eFuse counter, the bootloader refuses to load it.

    To release a firmware with a new anti-rollback version, increment CONFIG_BOOTLOADER_APP_SEC_VER and recompile. After the device OTAs to the new version and calls esp_ota_mark_app_valid_cancel_rollback(), the bootloader burns the new version into eFuse automatically.

    Bootloader Size Constraints

    The ESP32 second-stage bootloader must fit in IRAM that is available before flash is mapped. Default limit is 128 KB. If you add significant custom code to the bootloader (custom crypto, extended signature checking), check size:

    After build, check bootloader size

    ls -la build/bootloader/bootloader.bin

    Must be under 128 KB (131,072 bytes)

    For most projects, the default bootloader with secure boot and flash encryption enabled is 50–80 KB — well within limits.

    Manufacturing and Field Recovery

    Design your recovery process before production:

  • 1. Manufacturing flash: Use esptool.py to flash the factory partition plus a signed OTA image into ota_0 during manufacturing test. Burn eFuses for secure boot and flash encryption as the final manufacturing step.
  • 2. Field recovery: If both OTA partitions are corrupt (two consecutive failed updates with power loss), the bootloader falls back to the factory partition. The factory image must be a minimal, stable release that can bootstrap a fresh OTA download.
  • 3. Debug bypass: During development, maintain a set of devices without eFuses burned. Debug workflow requires JTAG access to the secure boot key material — impossible on production-fused devices.
  • The combination of secure boot, flash encryption, A/B OTA with rollback, and anti-rollback counters gives you a hardened, field-recoverable firmware update system suitable for security-conscious deployments. For the partition table design that supports this bootloader architecture, see our guide on [ESP32 partition table design](/esp32-partition-table-firmware).

    [Contact Code Caracal](/contact) — we build production firmware for clients across 15+ countries.

    Written by CodeCaracal Engineering

    We write from production experience — every technique in our articles has been deployed to real clients. No academic theory.

    More Articles

    Business · 12 min read

    IoT Device Compliance: FCC, CE, and Product Certification Guide for Hardware Startups

    Business · 11 min read

    What to Look for When Hiring an IoT Development Partner: 8 Critical Criteria

    Business · 11 min read

    IoT MVP to Production: Realistic Timeline and Budget for Hardware Startups

    Business · 11 min read

    IoT Development Agency vs Building In-House: A Decision Framework for Founders

    IoT Dashboard · 13 min read

    Next.js IoT Analytics Dashboard: From Sensor Data to Production App

    Business · 11 min read

    How Much Does It Cost to Build an IoT Product in 2024? A Realistic Breakdown

    IoT Dashboard · 11 min read

    IoT Dashboard UX: Design Principles for Industrial Monitoring Interfaces

    IoT Dashboard · 12 min read

    Node.js WebSocket Server: The Real-Time Backend for IoT Dashboards

    Cloud & DevOps · 12 min read

    Containerizing IoT Backend Services with Docker: From Dev to Production

    IoT Dashboard · 14 min read

    Grafana + InfluxDB IoT Monitoring: Complete Production Setup Guide

    IoT Dashboard · 12 min read

    Building Real-Time IoT Dashboards with React and Recharts

    Cloud & DevOps · 13 min read

    CI/CD for Embedded Firmware: Automated Build, Test, and OTA Release Pipeline

    Mobile Development · 12 min read

    Flutter Offline-First IoT Apps: Hive + Sync Architecture That Works in the Field

    Cloud & DevOps · 14 min read

    Terraform for IoT Infrastructure: Provisioning AWS IoT Core, Lambda, and InfluxDB as Code

    Mobile Development · 10 min read

    Flutter IoT Alerts: Firebase Push Notifications for Device Events

    Cloud & DevOps · 12 min read

    Deploying IoT Backends on AWS: ECS Fargate vs Lambda vs EC2 Decision Guide

    Mobile Development · 11 min read

    Flutter + MQTT: Building Production IoT Mobile Apps That Scale

    Mobile Development · 13 min read

    Flutter BLE: Building a Bluetooth IoT Controller App from Scratch

    Cloud & DevOps · 13 min read

    AWS IoT Core vs Azure IoT Hub vs Google Cloud IoT: 2024 Honest Comparison

    IoT Engineering · 13 min read

    Kafka vs RabbitMQ for IoT: Choosing the Right Message Queue for High-Volume Telemetry

    IoT Engineering · 14 min read

    IoT System Testing: Unit, Integration, Hardware-in-the-Loop, and End-to-End

    IoT Engineering · 14 min read

    Predictive Maintenance with IoT Sensor Data: From Threshold to Machine Learning

    IoT Engineering · 14 min read

    Multi-Tenant IoT Platform Architecture: Isolation, Scaling, and Data Partitioning

    Embedded Systems · 14 min read

    Memory Management in Embedded Firmware: Avoiding Heap Fragmentation and Stack Overflows

    IoT Engineering · 13 min read

    IoT Cost Optimization: How We Cut AWS IoT Bills by 60% Without Sacrificing Reliability

    IoT Engineering · 12 min read

    Edge Computing in IoT: When to Process On-Device vs In the Cloud

    IoT Engineering · 13 min read

    Digital Twins for IoT: Building a Virtual Mirror of Your Physical Devices

    Embedded Systems · 14 min read

    ESP32 Deep Sleep Mastery: Cutting Power Consumption from 240mA to 10µA

    IoT Engineering · 10 min read

    MQTT QoS 0, 1, and 2 Explained: Choosing the Right Level for IoT

    IoT Engineering · 14 min read

    IoT Monitoring and Observability: Metrics, Logs, and Distributed Tracing

    Embedded Systems · 14 min read

    Debugging Embedded Firmware: JTAG, GDB, Logic Analyzers, and Serial Tracing

    IoT Engineering · 12 min read

    WebSocket vs MQTT vs Server-Sent Events: Real-Time IoT Protocol Deep Dive

    Embedded Systems · 13 min read

    STM32 HAL vs Low-Level Drivers: When the Abstraction Costs You Too Much

    IoT Engineering · 13 min read

    IoT Data Pipeline: From Raw Sensor Reading to Live Dashboard in Under 100ms

    IoT Engineering · 13 min read

    Zero-Touch IoT Device Provisioning: Scaling from 10 to 100,000 Devices

    Embedded Systems · 13 min read

    UART vs SPI vs I2C: Choosing the Right Protocol for Sensor Integration

    IoT Engineering · 12 min read

    Real-Time IoT Alerting: From Simple Thresholds to ML Anomaly Detection

    Embedded Systems · 12 min read

    ESP32 Partition Table: Designing Flash Layout for Production Firmware

    IoT Engineering · 12 min read

    IoT Architecture Patterns: Hub-and-Spoke, Mesh, and Edge-Cloud Hybrid

    Embedded Systems · 13 min read

    IoT Battery Life Optimization: Engineering Devices That Last Years on a Single Charge

    IoT Engineering · 13 min read

    Time-Series Databases for IoT: InfluxDB vs TimescaleDB vs AWS Timestream

    Security · 14 min read

    Zero-Trust Security for Embedded IoT: Why Your Devices Are Probably Vulnerable

    Embedded Systems · 14 min read

    FreeRTOS on ESP32: Task Scheduling, Queues, and Resource Management for IoT

    IoT Engineering · 12 min read

    Building a Production IoT Gateway with Raspberry Pi and Node.js

    Embedded Systems · 13 min read

    ESP32 vs STM32: Choosing the Right Microcontroller for Your IoT Project

    Mobile Development · 10 min read

    Flutter + WebSocket: Building Real-Time IoT Dashboards That Don't Stutter

    IoT Engineering · 13 min read

    IoT Fleet Management at Scale: AWS IoT Core Device Registry and Provisioning

    IoT Engineering · 11 min read

    MQTT vs HTTP for IoT: Which Protocol Wins in Production?

    IoT Engineering · 12 min read

    ESP32 → MQTT → AWS IoT Core: The Production-Grade Architecture Guide

    Let's Build Together

    Got an IoT challenge?
    We've shipped it.

    Whether you need a fleet to track, a factory to monitor, or a farm to automate — our team has done it before and we'd love to build it with you. Typical response time: under 24 hours.

    No upfront commitment99.9% uptime SLANDA on requestFixed-price options