Back to Blog
Embedded Systems

ESP32 Partition Table: Designing Flash Layout for Production Firmware

The default ESP32 partition table is fine for demos. Production firmware needs a carefully designed flash layout with OTA partitions, factory reset support, NVS for device configuration, and enough headroom for firmware growth. Here is how to design it right.

March 25, 2024
12 min read
ESP32Partition TableOTANVS

ESP32 Partition Table: Designing Flash Layout for Production Firmware

The partition table is one of those things that almost every tutorial glosses over. They use the default partition table, flash the demo, and move on. But when you are shipping production firmware to thousands of devices, your partition layout is a critical architectural decision that determines whether OTA updates work reliably, whether devices can recover from bad firmware, and whether you have enough space for firmware growth over the product lifetime.

Understanding the Partition Table Format

The ESP32 partition table is a binary structure stored at address 0x8000 in flash. It is generated from a CSV file during the build process. Each row defines one partition.

Name, Type, SubType, Offset, Size, Flags

nvs, data, nvs, 0x9000, 0x6000, otadata, data, ota, 0xf000, 0x2000, phy_init, data, phy, 0x11000, 0x1000, factory, app, factory, 0x10000, 1M, ota_0, app, ota_0, 0x110000, 1M, ota_1, app, ota_1, 0x210000, 1M, coredump, data, coredump, 0x310000, 64K, storage, data, spiffs, 0x320000, 960K,

This layout targets a 4 MB flash (the most common ESP32 module size). Let us walk through each partition.

Key Partitions Explained

nvs (Non-Volatile Storage): This is where ESP-IDF stores WiFi credentials, device configuration, and any key-value data your firmware writes. 24 KB (0x6000) is the minimum; allocate more if you store significant configuration data.

otadata: 8 KB control structure that tracks which OTA partition to boot from and whether an OTA update is pending or confirmed. Never reduce this below 8 KB.

phy_init: RF calibration data written at manufacturing. Do not remove this.

factory: The baseline firmware that is always available. If both OTA partitions are invalid, the bootloader falls back here. This is your recovery partition.

ota_0 / ota_1: The A/B update partitions. OTA updates write to whichever slot is not currently running, then the bootloader switches to the new slot on next boot.

coredump: When firmware crashes, the core dump is written here. Retrieve it over serial or upload to a crash analytics server.

storage (SPIFFS/LittleFS): For storing configuration files, certificates, HTML for captive portals, etc.

Sizing OTA Partitions

Your OTA partitions must be at least as large as your largest expected firmware binary — with room to grow. Common mistake: setting OTA partitions to 1 MB when the firmware is already 900 KB and you have six months of features to add.

Rule of thumb: OTA partition size = current firmware size × 2, rounded up to the nearest 64 KB boundary.

For most production ESP-IDF projects:

  • Simple sensor firmware: 512 KB OTA partitions
  • WiFi + TLS + MQTT + OTA: 1 MB OTA partitions
  • WiFi + TLS + MQTT + BLE + large feature set: 1.5 MB OTA partitions
  • On a 4 MB flash with 1 MB OTA partitions: you use 1 MB (factory) + 1 MB (ota_0) + 1 MB (ota_1) = 3 MB for firmware, leaving 1 MB for NVS, otadata, phy, coredump, and storage. That is tight. Consider 8 MB flash modules (ESP32-WROVER) for firmware-heavy projects.

    Reading and Writing NVS from C

    NVS uses a namespace/key/value model. Always use namespaces to avoid key collisions between components.

    #include "nvs_flash.h"
    #include "nvs.h"

    #define NVS_NAMESPACE "device_cfg"

    esp_err_t config_write_wifi_credentials(const char *ssid, const char *password) { nvs_handle_t handle; esp_err_t err;

    // Open namespace in read-write mode err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle); if (err != ESP_OK) return err;

    err = nvs_set_str(handle, "wifi_ssid", ssid); if (err != ESP_OK) goto cleanup;

    err = nvs_set_str(handle, "wifi_pass", password); if (err != ESP_OK) goto cleanup;

    // Commit changes — mandatory for writes to be persisted err = nvs_commit(handle);

    cleanup: nvs_close(handle); return err; }

    esp_err_t config_read_wifi_credentials(char *ssid, size_t ssid_len, char *pass, size_t pass_len) { nvs_handle_t handle; esp_err_t err;

    err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle); if (err != ESP_OK) return err;

    err = nvs_get_str(handle, "wifi_ssid", ssid, &ssid_len); if (err != ESP_OK) goto cleanup;

    err = nvs_get_str(handle, "wifi_pass", pass, &pass_len);

    cleanup: nvs_close(handle); return err; }

    Always handle ESP_ERR_NVS_NOT_FOUND: On first boot, keys do not exist. Your firmware must handle this gracefully and enter a provisioning mode.

    OTA Update Flow in Code

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

    void perform_ota_update(const char *firmware_url) { esp_https_ota_config_t ota_config = { .http_config = &(esp_http_client_config_t){ .url = firmware_url, .cert_pem = server_root_ca, // Verify server cert .timeout_ms = 10000, .keep_alive_enable = true, }, };

    ESP_LOGI("OTA", "Starting OTA from: %s", firmware_url); esp_err_t ret = esp_https_ota(&ota_config);

    if (ret == ESP_OK) { ESP_LOGI("OTA", "OTA succeeded — restarting"); esp_restart(); } else { // OTA failed — current firmware still running, no damage done ESP_LOGE("OTA", "OTA failed: %s", esp_err_to_name(ret)); } }

    After restart, the new firmware must call esp_ota_mark_app_valid_cancel_rollback() to confirm it boots successfully. If it does not (because it crashes), the bootloader automatically rolls back to the previous partition. This is your safety net.

    Factory Reset Partition Strategy

    Store a known-good firmware image in the factory partition at manufacturing time. In the field, if both OTA partitions become corrupted (power loss during two consecutive updates), the bootloader boots factory firmware. From there, the device re-downloads a fresh OTA image.

    To trigger a factory reset from firmware:

    void factory_reset(void) {
        // Erase otadata — bootloader will boot factory partition next
        const esp_partition_t *otadata = esp_partition_find_first(
            ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, "otadata");

    if (otadata) { esp_partition_erase_range(otadata, 0, otadata->size); }

    nvs_flash_erase(); // Clear all device configuration esp_restart(); }

    A well-designed partition table is the foundation of a reliable OTA update system. Invest time here before your first production build — changing it later requires a full reflash of every device in the field.

    For deep-dive coverage of secure boot and flash encryption layered on top of this partition layout, see our guide on [IoT bootloader design and secure boot](/iot-bootloader-secure-boot).

    [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

    Embedded Systems · 14 min read

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

    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

    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