Software
ESP32 Lifecycle Manager V2.1: OTA Trust and Signing, Explained
The follow-up to the V2.0 setup guide: how Lifecycle Manager decides which firmware it trusts, Official Mode vs. Custom Publisher Mode, and how to sign your own OTA updates.
The Lifecycle Manager V2.0 guide walks through the Docker/ESP-IDF setup and your first flash. This guide picks up where that one left off and covers the part most OTA tutorials skip entirely: how Lifecycle Manager decides which firmware it’s allowed to install, and why that matters before you rely on OTA for anything real.
What we're building
An ESP32 that updates itself over Wi-Fi, verifies firmware before installing it, and only accepts updates it actually trusts. That trust doesn’t happen automatically, Lifecycle Manager always needs a rule for what firmware is allowed to run, and this guide is really about two things: how updates are delivered, and how they’re trusted.
Two modes: Official vs. Custom Publisher
Lifecycle Manager (LCM) can run in two different modes, and understanding the difference is the key to everything else in this guide.
Official Mode (default, easiest)
LCM ships with a trusted public key already built in. It will only accept firmware signed with the matching official private key. No key generation, no rebuilding LCM, works out of the box. Good for beginners and normal end users, but it does not mean “accepts any firmware you build yourself”, it means “accepts firmware signed by the official signer”.
Custom Publisher Mode (advanced)
You generate your own signing keys, rebuild LCM with your own public key embedded, and sign your own firmware with your own private key. Full control, but extra steps. You can’t remove trust from the system entirely, you can only choose whose key LCM trusts.
If the firmware’s signature doesn’t match what LCM trusts, in either mode, the update is rejected. That’s not a bug, that’s the protection working as intended.
What you need
Hardware, one of: an ESP32-WROOM-32 development board (USB and buttons built in, the easiest option), or a bare ESP-WROOM-32 module with a USB programmer/baseboard (more control, no onboard flash button). If unsure, use the development board.
Also: a USB data cable (not charge-only), a 2.4GHz Wi-Fi network, and an iPhone or iPad with Apple Home if you want to try the HomeKit LED example.
Software: Docker Desktop, Git, Python 3 and esptool.py. ESP-IDF itself is not installed manually, Docker handles that.
Docker and the ESP-IDF image
Install Docker Desktop from docker.com, restart after installing, then confirm it works:
docker --version
docker ps
An empty list from docker ps is the correct, expected result. Then pull a fixed ESP-IDF version, so the setup stays predictable:
docker pull espressif/idf:v5.4.2
This only downloads a Docker image, nothing is installed on your computer directly.
Git, Python and esptool.py
Install Git and confirm with git --version. Docker handles building firmware, but flashing it onto real hardware over USB is easiest from your own computer, so also install Python 3 (tick “Add Python to PATH” and “Install pip”), then:
pip3 install esptool
esptool.py version
Get the Lifecycle Manager code
git clone https://github.com/AchimPieters/esp32-lifecycle-manager.git
cd esp32-lifecycle-manager
Keep the folder name exactly as cloned, a renamed folder breaks the Docker volume mount used later.
Do you need to create keys?
Not always. If you’re using Official Mode, no, you don’t need to create keys, edit LCM’s source, or rebuild it, go straight to the build step below.
If you want to publish and distribute your own signed firmware, you do: generate a private/public key pair, embed the public key into LCM, rebuild it, and sign your own firmware with the private key. That’s Custom Publisher Mode, covered next.
Custom Publisher Mode (optional, advanced)
Skip this section entirely if you’re using Official Mode. To publish your own firmware, generate a key pair:
openssl ecparam -name prime256v1 -genkey -noout -out ota_signing_private.pem
openssl ec -in ota_signing_private.pem -pubout -out ota_signing_public.pem
The private key signs firmware, the public key is what LCM uses to verify it, if the two don’t match, every update fails verification. When firmware is signed, the file is hashed, the hash is signed with the private key, and a matching .sig file is produced. LCM later re-downloads the firmware, re-hashes it, and checks the signature against the embedded public key, rejecting anything that doesn’t match.
To embed your own public key, open main/github_update.c, find the public key definition, and replace it with your own, copying the full PEM text exactly, including the BEGIN/END PUBLIC KEY lines and all line breaks. Wrong formatting means every signature check fails.
Build, erase and flash
Start the ESP-IDF container and build:
docker run -it -v ~/esp32-lifecycle-manager:/project -w /project espressif/idf:v5.4.2
idf.py set-target esp32
idf.py build
Replace esp32 with esp32s2, esp32s3 or esp32c3 for a different chip family. Keep the terminal open, the build output includes the exact flash command you’ll need. Before flashing, erase the board from a normal terminal (outside Docker):
esptool.py erase_flash
If it can’t connect, hold BOOT, tap RESET, release BOOT, then retry, that’s a common way to force a board into programming mode. Then copy the flash command the build printed and run it outside Docker, it writes the bootloader, partition table, OTA metadata and the LCM firmware itself in one go.
Watch it boot
Open a serial monitor to see what the board is doing:
screen /dev/cu.usbserial-XXXX 115200
Substitute your real port (macOS: /dev/cu.usbserial-XXXX or /dev/cu.SLAB_USBtoUART, Linux: /dev/ttyUSB0//dev/ttyACM0, Windows: the matching COM port). Press RESET if nothing appears immediately. A normal boot shows chip startup, memory init, and LCM starting its access point and HTTP server. To exit screen: Ctrl+A, then K, then Y.
Once running, LCM starts a captive-portal access point (look for a network named LCM-XXXXXX), connect to it and configure your Wi-Fi credentials and firmware source there.
How OTA works, in both modes
LCM downloads the firmware file and its signature, verifies the signature, and only installs if it matches: against the built-in official public key in Official Mode, or your embedded public key in Custom Publisher Mode. A common misunderstanding is thinking you can build your own firmware and have Official-Mode LCM accept it automatically, it won’t, LCM never accepts unsigned firmware in either mode.
LCM needs to find two files, main.bin and main.bin.sig, at a configured source, typically a GitHub repository given as username/repository (for example AchimPieters/esp32-test), with both files present in a release and filenames matching exactly.
Build and sign example firmware
Rather than starting a new project, use the repository’s own included example, a full HomeKit LED accessory, not just a bare blink demo:
cd examples/led
idf.py set-target esp32
idf.py build
If you’re using Official Mode as an end user, you’d normally install firmware already signed by the trusted publisher rather than signing your own. In Custom Publisher Mode, sign it yourself:
cd ~/esp32-lifecycle-manager
./generate_sig.sh examples/led/build/main.bin ota_signing_private.pem
This produces main.bin and main.bin.sig, both required, LCM can’t verify an update missing the .sig file. If OTA fails, the most common causes are a missing .sig upload, the wrong repository name, or signing with a different key than the one embedded in LCM. To publish, create a GitHub Release and upload both files.
Hardware: wire up something visible
You need an LED (any color), a 220Ω–1kΩ resistor, two jumper wires, and a breadboard. The LED HomeKit example is typically configured for GPIO2 (also often the board’s built-in LED, though not always). Wire it as GPIO2 → resistor → LED → GND: connect the LED’s long leg through the resistor to GPIO2, and its short leg to GND. If the LED is backwards, nothing breaks, it just won’t light up.
Plug the ESP32 into USB, it boots immediately and starts running whatever LCM has installed.
Add the accessory to Apple Home
Make sure the ESP32 is powered, connected to Wi-Fi, and your iPhone/iPad is on the same network. Then in the Home app: tap +, choose Add Accessory, scan the example’s QR code, and select the new accessory once it appears.
Apple will likely warn that the accessory “is not certified and may not work reliably”, that’s expected for any DIY HomeKit device, not a sign something is wrong. Tap Add Anyway to continue, assign a room and name, then test it: tap on/off and confirm the LED responds.
Update, reset and recovery
Lifecycle Manager supports a firmware-update mechanism, a software factory reset (clears configuration and restarts), and hardware factory-reset behavior via repeated-reboot detection, depending on your build configuration. Test whichever recovery path you plan to rely on before depending on it in a real deployment, the exact behavior depends on your own build and setup.
Summary
Official Mode is the easiest path for end users: no key setup, but only officially signed firmware installs. Custom Publisher Mode is more work, but gives you full control over your own signed firmware. Either way, the core design is the same: Lifecycle Manager never installs unsigned or unverified firmware, that rejection is the protection doing its job, not a failure.
Related
Electronics
0.91-inch OLED Display on an ATtiny85
The ATtiny85 is a compact and low-power microcontroller ideal for minimalist electronics.
Read more
Software
ESP32 Lifecycle Manager
ESP32-Lifecycle-Manager (LCM) is a smart “control tower” for your ESP32 device. At startup, LCM takes charge first: it handles network setup, update management, recovery procedures, and more, so your application can focus on its…
Read more
Opinion
Why You (Should or Shouldn’t) Buy a Smart Plug from Action
Budget smart plugs from Action and HEMA are cheap because they're all the same Tuya white-label hardware behind the scenes, and that convenience comes with your data routing through Tuya's servers. Here's what's actually inside one, and why reflashing it with your own firmware is the better deal.
Read more