HttpsOta
Over-the-air updates for ESP32, over TLS, with rollback
An OTA update library written for a device that could not be reached physically — so every failure mode that would brick it had to be handled before the first update was ever sent.
Problem
An update to a device you can physically reach is an inconvenience when it fails. An update to a device on another continent is the end of the device.
The naive OTA path has several ways to end that way: a truncated download that verifies as complete, a stalled connection that never times out, a valid image that does not boot, a TLS check skipped because the device's clock is wrong.
What I built
A library whose feature list is a list of the ways this can go wrong.
- SHA-256 verification of the downloaded image
- Download stall detection and total timeouts
- Progress callbacks
- Dual OTA partition support with automatic rollback
- TLS validation, with the certificate time dependency handled explicitly
- Heap guards, and handling for partition table failures
- Chunked transfer considerations
- An HMAC signing example
The failure analysis is the documentation
The README is written as an account of what breaks rather than a feature list — the certificate time dependency, the partition table failure mode, what a stalled chunked transfer looks like from the device's side. That is the part worth reading.
An update, and every way it can fail
Each branch here is a way a naive OTA bricks a device you cannot reach. The library exists because this specific device was on another continent.
Poll the update endpoint
The server returns version, sha256 and size.
Is there enough heap for a TLS handshake?
No — refuse to start. LowMemoryYes — open the connectionDownload into the inactive OTA slot
Hashed while downloading, and sized from Content-Length.
Connection dies mid-download — Stalled, retry laterTotal timeout exceeded — give up rather than hangVerify SHA-256
Mismatch — truncated or tampered. Nothing is installedMatch — mark the new slot bootableReboot into the new image
It comes up and reports its version — doneIt does not — automatic rollback to the running slot
Failure modes, and what the device does
A truncated image and a tampered one look identical from the device, so both are rejected the same way — before install, never after.
| Result | Cause | Consequence |
|---|---|---|
| Stalled | Network died mid-download | Nothing installed; retry later |
| HashMismatch | Truncated or tampered image | Nothing installed |
| LowMemory | Not enough contiguous heap for TLS | Handshake never started |
| Rollback | New image did not boot | Device returns to the running slot |
Things that bite you once
Documented because each cost real debugging time, and the symptom points somewhere other than the cause.
| Symptom | Actual cause |
|---|---|
| `No bootable app partitions in the partition table` | Looks like corrupt flash; is really an off-by-one partition table |
| Update reinstalls on every poll | The device is not reporting the version it is running |
| Write fails part-way | `Content-Length` missing — the device sizes its partition write from it |
| TLS validation fails on a fresh device | The certificate check is time-dependent and the clock is not set |
Limitations and failure modes
- Rollback protects against an image that does not boot. It cannot protect against an image that boots and then loses its network configuration.
Attribution
- Built by me
- The library, extracted from the PrabalOS deployment it was written for.
Related
- PrabalOS
A connected ESP32 device for staying in touch