Firmware updates over HTTPS for ESP32 and ESP32-S3 with ESP-IDF. Images are signed, the server is authenticated, older versions are refused, and a new image that fails its self-test is rolled back automatically.
sequenceDiagram
participant D as Device
participant S as Update server
D->>S: HTTPS GET image (server certificate checked)
S-->>D: Image header
Note over D: Same project? Newer version?<br/>If not, stop here
S-->>D: Rest of the image, written to the inactive slot
Note over D: Check hash and signature,<br/>then select the new slot
D->>D: Restart into the new image (pending verify)
D->>S: Self-test: join the network in time
alt self-test passes
Note over D: Mark the image valid
else self-test fails or times out
Note over D: Mark it invalid and restart into the previous image
end
- Server identity. TLS against the public CAs of the ESP-IDF certificate bundle, or against one pinned CA. Plain HTTP is refused.
- Image origin. Only images signed with the release key are installed (
CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT): ECDSA P-256 on ESP32, RSA-3072 on ESP32-S3. - Image integrity. The SHA-256 in the image is checked before the new slot is selected, so an interrupted download never boots.
- Project and version. The image header is read before the download continues. Images for another project, or not newer than the running app by semantic-version rules, are rejected. eFuse anti-rollback can be added on top.
- Health after the update. A new image must join the network within
CONFIG_SOTA_WIFI_TIMEOUT_MS, or the bootloader returns to the previous image.
The threat model lists what each check protects against and what needs Secure Boot V2 and flash encryption.
components/secure_ota/ update logic, reusable in other projects
secure_ota.c download, checks, install, boot confirmation
secure_ota_version.c semantic versions and the install decision (plain C)
main/ demo app: Wi-Fi, self-test, periodic update checks
test/host/ unit tests for the version logic, run on a PC
tools/ test certificates and a local HTTPS server
partitions.csv two OTA slots, no factory app (4 MB flash)
Requires ESP-IDF 5.x or 6.x.
-
Create a signing key in the project folder. Keep it secret; git ignores it.
# ESP32: ECDSA P-256 openssl ecparam -name prime256v1 -genkey -noout -out secure_boot_signing_key.pem # ESP32-S3: RSA-3072 openssl genrsa -out secure_boot_signing_key.pem 3072
-
Select the chip and set Wi-Fi and the firmware URL under Secure OTA in menuconfig:
idf.py set-target esp32s3 idf.py menuconfig
-
Build, flash and watch the log:
idf.py build flash monitor
The build signs build/secure_ota_demo.bin; that file is what you publish.
- Raise the version in
version.txt, for example to1.1.0. - Build. The image is signed with the release key.
- Upload
build/secure_ota_demo.binto the firmware URL. Devices install it at their next check (CONFIG_SOTA_CHECK_INTERVAL_S).
tools/make_test_certs.sh 192.168.1.20 # address of the PC that serves the image
cp test-certs/ca.pem main/certs/server_ca.pem # then select "Only the CA in main/certs/server_ca.pem"
python tools/serve_https.py build --cert test-certs/server.pem --key test-certs/server.keySet the firmware URL to https://192.168.1.20:8443/secure_ota_demo.bin. Restore main/certs/server_ca.pem before committing.
Copy components/secure_ota into your project and call it from your app:
#include "secure_ota.h"
secure_ota_confirm_boot(self_test_passed()); // once at start-up
const secure_ota_config_t config = {
.firmware_url = "https://updates.example.com/app.bin",
.server_ca_pem = NULL, // public CAs from the certificate bundle
.timeout_ms = 10000,
};
if (secure_ota_update(&config) == SECURE_OTA_INSTALLED) {
esp_restart();
}Copy the rollback, signing and partition settings from sdkconfig.defaults as well.
The default configuration burns no eFuses, so a development board stays reusable. Devices in the field should also enable:
- Secure Boot V2, so only signed bootloaders and apps run, even after flashing over the serial port
- Flash encryption, so the image and the Wi-Fi credentials in NVS cannot be read out of the flash chip
- Anti-rollback (
CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK), with a security version that rises with each security fix
These are one-way changes. Follow the ESP-IDF security guides and try them on spare boards first.
Unit tests for version parsing, semantic-version precedence and the install decision run on a PC, with AddressSanitizer and UBSan in CI:
cmake -S test/host -B build-host -DSECURE_OTA_SANITIZE=ON
cmake --build build-host
ctest --test-dir build-host --output-on-failureCI also builds and signs the firmware with ESP-IDF 5.5 and 6.0 for ESP32 and ESP32-S3, using a throwaway key.
MIT. See LICENSE.