Data as of Aug 25, 2026 · Based on 320 AI responses from ChatGPT Search and Google AI Mode · See how Parse measures this
AWS IoT is the best fit when you need a cloud update server to host firmware, manage device cohorts, and orchestrate staged rollouts. If you want a provider to handle hosting and delivery with less setup, Golioth is recommended, though evidence shows less emphasis on deployment orchestration.
Brands AI recommends here
Best when you need a secure cloud update server to host firmware, manage device cohorts, and orchestrate staged rollouts; evidence cites AWS IoT for server-side deployment and staging. Tradeoff: setup and cloud dependency.
Best when you prefer a managed OTA provider that handles hosting and delivery for embedded fleets with minimal infrastructure work; evidence notes Golioth provides managed OTA services. Caveat: less detail on orchestration.
Implementing over-the-air (OTA) firmware updates for a fleet of embedded devices requires a robust, secure, and fault-tolerant architecture spanning the device, the communication pipeline, and the cloud backend.
Here is a practical, step-by-step blueprint to build an end-to-end OTA update system.
To prevent "brick-bricked" devices during a failed or interrupted update, your memory layout must support safe rollbacks.
Never push raw binary files (.bin) over the air. You need a secure payload format.
Choose a lightweight, resilient protocol for device-to-cloud connectivity.
Pushing updates to 100% of your fleet simultaneously is a high-risk gamble.
Building an OTA pipeline from scratch is time-consuming and prone to security oversights. Evaluate whether to build or adopt an existing framework:
If you'd like, let me know:
I can tailor the exact bootloader or cloud strategy to your stack.
No sources cited
Implementing OTA (over-the-air) firmware updates for an embedded fleet is less about the file transfer and more about building a safe update lifecycle: release management, device authentication, secure delivery, verification, installation, rollback, and fleet monitoring. A production OTA system should assume that devices can lose power, networks can fail, and attackers may try to install unauthorized firmware.
A typical architecture looks like this:
Firmware CI/CD Pipeline
|
v
Signed Firmware Artifact
|
v
OTA Update Server / Cloud
|
+-----------+-----------+
| |
Device A Device B
| |
OTA Agent + Bootloader + Secure Storage
|
Verify -> Install -> Reboot -> Confirm
Your device needs three main components:
The bootloader is the trusted component that decides whether new firmware is allowed to run.
Responsibilities:
For many MCUs, use a proven bootloader rather than writing your own. Examples include MCUboot-based designs, which support signed images and rollback workflows.
A small application task/service that:
Store things like:
current_version = 1.4.2
pending_version = 1.5.0
boot_attempts = 2
update_state = testing
Use nonvolatile storage with wear considerations.
For reliability, avoid overwriting the currently running firmware.
Example:
Flash:
+----------------+
| Bootloader |
+----------------+
| Firmware A | <-- running
+----------------+
| Firmware B | <-- update target
+----------------+
| Settings |
+----------------+
Update flow:
If anything fails, boot back into A.
This prevents a power loss during flashing from permanently bricking the device.
Never allow a device to install an arbitrary binary downloaded from a server.
A secure update package usually contains:
firmware.bin
manifest.json
{
"device_family": "sensor-v2",
"version": "1.5.0",
"size": 842193,
"sha256": "...",
"minimum_bootloader": "2.0",
"signature": "..."
}
The device should verify:
Digital signatures are preferred because the device only needs the vendor public key; the signing private key remains in your release infrastructure.
Typical choices:
Your server should manage:
Example:
Firmware
---------
ID: fw-150
Version: 1.5.0
Hardware: sensor-v2
Status: staged
SHA256: abc123
Signature: xyz789
Track:
Device ID
Hardware revision
Current firmware
Last check-in
Battery level
Update status
Examples:
This is called a staged rollout or canary deployment.
A common sequence:
Device -> Server
{
id: "device123",
firmware: "1.4.2",
battery: 82%
}
Server -> Device
{
update_available: true,
url: "...",
version: "1.5.0",
checksum: "...",
signature: "..."
}
Use:
The transport protects the communication channel, but firmware signing protects the firmware itself.
Your device should answer:
"What happens if the update fails?" Common rollback triggers:
Example:
Boot new firmware
|
v
Health check OK?
/ \
Yes No
| |
Confirm Rollback
Also consider anti-rollback protection:
Installed version: 5
Reject:
version 4
version 3
This prevents attackers from reinstalling vulnerable old firmware.
Your firmware signing key is one of your most important assets.
Good practice:
A compromised signing key can become a fleet-wide compromise.
At fleet scale, you need visibility:
Track:
Example dashboard:
Firmware 1.5.0 rollout
Target devices: 100,000
Installed: 93,200
Pending: 5,400
Failed: 1,100
Offline: 300
Before deployment, test:
A good OTA design treats failure recovery as a primary feature, not an exception.
Depending on your platform:
For a new connected product, a solid baseline would be:
That architecture scales from hundreds to millions of devices and is aligned with common secure firmware update guidance.
If you share your hardware platform (e.g., ESP32, STM32, nRF52, embedded Linux, RTOS) and connectivity type (Wi-Fi, cellular, BLE gateway, LoRaWAN), the implementation details can be narrowed down substantially.
A robust OTA system for an embedded fleet is essentially a secure software-delivery pipeline + a fault-tolerant bootloader + fleet orchestration.
A good architecture looks like this:
Developer
│
▼
CI/CD ──► Build firmware ──► Test ──► Sign artifact
│
▼
Firmware repository
│
▼
OTA control plane
┌────────────┴────────────┐
│ │
Device groups Rollout policy
│ │
▼ ▼
MQTT/HTTPS ◄────────────── Job
│
▼
┌─────────────────┐
│ Embedded device │
│ OTA agent │
│ Bootloader │
└────────┬────────┘
│
verify + install
│
▼
Reboot safely
│
health check / commit
│
▼
Report result
For most production devices, I'd strongly recommend A/B firmware partitions:
Flash
┌──────────────────────────────┐
│ Bootloader │ ← rarely changed
├──────────────────────────────┤
│ Slot A: firmware v1.4.2 │
├──────────────────────────────┤
│ Slot B: firmware v1.5.0 │ ← downloaded here
├──────────────────────────────┤
│ Device configuration/data │
└──────────────────────────────┘
The device continues running from A while downloading v1.5.0 into B.
Then:
This is much safer than overwriting the currently running firmware.
For devices with constrained flash, you can use other schemes, such as streaming updates or delta updates, but A/B is generally the easiest architecture to make reliable.
Never rely on HTTPS alone.
TLS protects the download channel, but you also need to protect against someone obtaining credentials or modifying artifacts in your infrastructure.
Your release pipeline should look roughly like:
source
↓
compile
↓
unit/integration tests
↓
firmware.bin
↓
SHA-256
↓
digital signature
↓
artifact repository
The device contains the public verification key, ideally protected by hardware-backed secure storage.
The firmware manifest might contain:
{
"product": "sensor-x",
"version": "1.5.0",
"hardware": "rev-b",
"size": 1834528,
"sha256": "...",
"signature": "...",
"min_bootloader": "2.1.0"
}
The device should reject the update if the signature is invalid, the hash doesn't match, the hardware isn't compatible, or the version violates your update policy.
NIST specifically recommends mechanisms that restrict updates to authorized entities and authenticate/verify update software, including mechanisms such as digital signatures and checksums.
Your embedded application shouldn't have to understand the entire deployment system.
Create a small OTA agent responsible for:
Conceptually:
ota_check_for_update();
if (update_available()) {
download_to_inactive_slot();
if (!verify_signature())
reject_update();
if (!verify_hash())
reject_update();
if (!compatible_with_hardware())
reject_update();
mark_slot_pending();
reboot();
}
After reboot:
if (firmware_health_check_passes()) {
bootloader_confirm_current_image();
} else {
bootloader_rollback();
}
AWS's current OTA documentation follows essentially this model: the device-side OTA agent receives the job, downloads the image, verifies its digital signature, installs it, and reports success/failure.
Don't send a 2 MB firmware binary through your MQTT command channel unless there's a compelling reason.
A common design is:
MQTT
│
└── "Firmware 1.5.0 is available"
│
▼
HTTPS
│
▼
firmware.bin
MQTT is good for:
HTTPS/object storage is generally better for:
AWS IoT Jobs, for example, can push OTA jobs using MQTT/HTTP, and its device-management system supports targeting and monitoring fleets.
This is where a prototype OTA system becomes a production OTA system.
Don't do:
Release v1.5.0
↓
ALL 500,000 devices
Instead:
v1.5.0
│
├── internal/test devices: 20
│
├── canary: 100
│
├── 1% of fleet
│
├── 10%
│
├── 25%
│
├── 50%
│
└── 100%
Define an automatic abort threshold.
For example:
Rollout:
1% → 10% → 25% → 50% → 100%
Abort if:
install_failure > 2%
boot_failure > 1%
crash_rate > 1%
connectivity drops significantly
AWS IoT Device Management Jobs supports rollout controls, deployment velocity, failure thresholds, scheduling, and fleet targeting.
Your server should know at least:
device_id
hardware_revision
firmware_version
bootloader_version
region
customer/tenant
deployment_group
last_seen
update_status
last_update_failure
Then you can target things like:
hardware_revision == "B"
AND firmware_version < "1.5.0"
AND region == "US"
rather than blindly updating the entire fleet.
Continuous deployment systems can also automatically target devices as they enter a particular group/version state.
Assume the device will lose power or connectivity at the worst possible moment.
Your OTA protocol should support:
Most importantly, never put the device into an unrecoverable state because an update was interrupted.
The old firmware should remain bootable until the new firmware has passed its health check.
A particularly useful state machine is:
IDLE
│
▼
DOWNLOADING
│
▼
DOWNLOADED
│
▼
VERIFIED
│
▼
PENDING_REBOOT
│
▼
BOOTING_NEW
│
├── health check OK ──► CONFIRMED
│
└── health check FAIL ─► ROLLBACK
The bootloader should have a boot-attempt counter:
pending image
boot attempt #1
boot attempt #2
boot attempt #3
│
└── still unhealthy
↓
rollback
This protects you from firmware that successfully downloads and authenticates but crashes immediately after boot.
The OTA security boundary should ideally be:
Root of trust
│
▼
Secure bootloader
│
├── verifies firmware signature
│
▼
Application firmware
Otherwise an attacker who can replace the bootloader can potentially bypass your firmware verification.
Also consider:
Keep the signing private key out of the devices and preferably out of ordinary developer machines.
Every OTA attempt should generate telemetry such as:
{
"device_id": "abc123",
"from_version": "1.4.2",
"to_version": "1.5.0",
"state": "confirmed",
"download_bytes": 1834528,
"download_duration": 42,
"boot_duration": 8,
"timestamp": "..."
}
Useful fleet metrics:
DOWNLOADINGThat lets you detect problems such as:
"v1.5.0 works perfectly on hardware rev C but causes boot failures on rev B."
Don't assume:
v1.0 → v2.0
is always valid.
Your manifest can specify:
{
"version": "2.0.0",
"hardware": ["rev-b", "rev-c"],
"min_bootloader": "1.7.0",
"requires": {
"config_schema": "4"
}
}
For incompatible releases, support migration paths:
v1 → v2
v2 → v3
rather than allowing:
v1 → v3
if v3 cannot understand v1's persistent data.
For example, on AWS:
CI/CD
│
▼
Firmware signing
│
▼
S3 / artifact store
│
▼
AWS IoT Jobs / OTA
│
┌──────┴──────┐
│ │
MQTT HTTPS
│ │
└──────┬──────┘
▼
OTA Agent
│
▼
Secure Bootloader
│
┌────┴────┐
│ │
Slot A Slot B
AWS IoT Device Management Jobs is specifically designed for fleet-wide remote operations and tracks individual job executions, while AWS's OTA tooling supports signed firmware and HTTP/MQTT delivery.
You could implement the same architecture independently with:
The cloud vendor isn't the important part—the security and failure semantics are.
If you're building this from scratch, I'd do it in this order:
The key principle is: an OTA update should be allowed to fail at every stage without permanently bricking the device. Authentication prevents malicious firmware; A/B + rollback prevents bad firmware; staged deployment prevents one bad release from taking down the whole fleet.
The OTA security boundary should ideally be:
Root of trust
│
▼
Secure bootloader
│
├── verifies firmware signature
│
▼
Application firmware
Otherwise an attacker who can replace the bootloader can potentially bypass your firmware verification.
Also consider:
Keep the signing private key out of the devices and preferably out of ordinary developer machines.
Every OTA attempt should generate telemetry such as:
{
"device_id": "abc123",
"from_version": "1.4.2",
"to_version": "1.5.0",
"state": "confirmed",
"download_bytes": 1834528,
"download_duration": 42,
"boot_duration": 8,
"timestamp": "..."
}
Useful fleet metrics:
DOWNLOADINGThat lets you detect problems such as:
"v1.5.0 works perfectly on hardware rev C but causes boot failures on rev B."
Don't assume:
v1.0 → v2.0
is always valid.
Your manifest can specify:
{
"version": "2.0.0",
"hardware": ["rev-b", "rev-c"],
"min_bootloader": "1.7.0",
"requires": {
"config_schema": "4"
}
}
For incompatible releases, support migration paths:
v1 → v2
v2 → v3
rather than allowing:
v1 → v3
if v3 cannot understand v1's persistent data.
For example, on AWS:
CI/CD
│
▼
Firmware signing
│
▼
S3 / artifact store
│
▼
AWS IoT Jobs / OTA
│
┌──────┴──────┐
│ │
MQTT HTTPS
│ │
└──────┬──────┘
▼
OTA Agent
│
▼
Secure Bootloader
│
┌────┴────┐
│ │
Slot A Slot B
AWS IoT Device Management Jobs is specifically designed for fleet-wide remote operations and tracks individual job executions, while AWS's OTA tooling supports signed firmware and HTTP/MQTT delivery.
You could implement the same architecture independently with:
Implementing over-the-air (OTA) firmware updates for embedded devices requires a robust, secure, and fault-tolerant architecture. A failure mid-update can "brick" a device, meaning your system must handle power losses, corrupted data, and rollbacks gracefully.
Core Architecture Components
Step-by-Step Implementation Roadmap
Recommended Open-Source Tools & Frameworks
If you'd like to dive deeper, let me know:
No sources cited
Yes. For a fleet, I’d design OTA as a secure, transactional software-delivery system, not simply “download a .bin file and flash it.”
A robust architecture looks like:
Developer
│
├─ build firmware
├─ test
└─ sign release
│
▼
┌─────────────────┐
│ Update backend │
│ │
│ • manifest │
│ • artifact CDN │
│ • device groups │
│ • rollout state │
│ • telemetry │
└────────┬────────┘
│ HTTPS/MQTT
▼
┌─────────────────┐
│ Device OTA │
│ agent │
│ │
│ check → download│
│ → verify → stage│
│ → reboot │
└────────┬────────┘
▼
┌─────────────────┐
│ Secure bootloader│
│ │
│ verify → boot │
│ → health check │
│ → commit/rollback
└─────────────────┘
The device should never execute an image merely because the OTA application downloaded it.
Use a hardware/root-of-trust-backed bootloader that verifies a cryptographic signature before booting the image. For example, MCUboot supports signed images and keeps the trusted public key in the bootloader.
The basic chain should be:
Firmware image
│
├── SHA-256 / equivalent hash
│
├── digital signature
│
▼
OTA agent
│
├── verify signature
├── verify hash
├── verify device/model compatibility
└── stage image
│
▼
bootloader
│
├── verify signature AGAIN
├── check anti-rollback counter
└── boot
The second verification is important: the bootloader, not your application, must ultimately decide what executable code is trusted.
For most products, this is the single biggest reliability improvement.
For example:
Flash
┌──────────────────────┐
│ Bootloader │
├──────────────────────┤
│ Slot A: running │ ← current firmware
├──────────────────────┤
│ Slot B: downloaded │ ← new firmware
├──────────────────────┤
│ OTA metadata │
└──────────────────────┘
The device downloads the new firmware into the inactive slot. It doesn't overwrite the currently working firmware.
Then:
MCUboot, for example, implements test swaps specifically to allow this kind of rollback after a bad boot.
This protects against:
AWS similarly recommends inactive partitions/atomic updates and rollback for resilient OTA systems.
Use TLS for the connection, but don't rely on TLS as your firmware authenticity mechanism.
TLS answers:
“Am I communicating securely with the server?”
Firmware signatures answer:
“Is this firmware authorized to execute?”
You want both.
A typical release might contain:
{
"product": "sensor-v2",
"hardware": "rev3",
"version": "4.7.2",
"security_counter": 109,
"size": 524288,
"sha256": "...",
"download": "...",
"signature": "..."
}
The device verifies that the manifest and firmware correspond to a trusted release.
For larger fleets, I'd seriously consider adopting the security model of The Update Framework (TUF) rather than inventing your own update-signing protocol. TUF is specifically designed to protect software update systems against repository/signing-key compromise and supports metadata, hashes, signatures, key delegation and expiration.
Rollback is useful for recovering from a bad update, but you don't want an attacker deliberately installing an old vulnerable version.
So distinguish:
Automatic rollback:
4.7.1 → 4.7.2
↓ bad
4.7.1 ← legitimate recovery
Malicious downgrade:
4.7.2 → 3.2.0
↓
REJECT
Use a monotonically increasing security counter or equivalent trusted version number.
MCUboot supports downgrade prevention using image versions/security counters.
A good rule is:
new_security_counter >= current_security_counter
with the counter stored somewhere the application cannot simply rewrite.
Don't have every device blindly ask:
“What's the newest firmware?”
Instead, model deployments explicitly:
Release 4.7.2
↓
10-device canary
↓
monitor
↓
100 devices
↓
1,000 devices
↓
10,000 devices
↓
100% fleet
Your backend should maintain things like:
For example:
AVAILABLE
↓
DOWNLOADING
↓
VERIFIED
↓
STAGED
↓
REBOOTING
↓
HEALTH_CHECK
├── success → COMMITTED
└── failure → ROLLED_BACK
This gives you fleet-wide observability instead of discovering weeks later that 8% of devices are running broken firmware.
AWS's IoT OTA architecture is one example of this model: jobs can target individual devices or groups and deployment progress can be monitored.
Assume networks will disappear halfway through a 2 MB download.
Don't do:
GET firmware.bin
↓
download 1.8 MB
↓
Wi-Fi dies
↓
start over
Instead:
GET firmware.bin
Range: bytes=...
↓
chunk 1
chunk 2
chunk 3
...
Store download progress and resume after reconnecting.
For very constrained devices, consider delta updates so you transfer only the changed portions of the firmware. AWS notes this approach can substantially reduce bandwidth and energy consumption for constrained networks.
I'd make the OTA agent roughly:
while (1) {
if (!update_available())
sleep_or_continue_normal_operation();
manifest = fetch_manifest();
if (!verify_manifest(manifest))
reject_update();
if (!compatible_with_hardware(manifest))
reject_update();
if (manifest.security_counter < current_counter)
reject_update();
download_to_inactive_slot();
if (!verify_image_hash())
discard_image();
if (!verify_image_signature())
discard_image();
mark_image_as_pending();
reboot();
}
Then the bootloader handles:
if (pending_image &&
signature_valid(pending_image) &&
version_allowed(pending_image)) {
boot_pending_image();
}
else {
boot_known_good_image();
}
And the new firmware must eventually do something equivalent to:
perform_startup_self_tests();
if (self_tests_pass())
mark_update_confirmed();
If the device reboots repeatedly before confirmation, the bootloader rolls back.
Test this aggressively.
During every stage, randomly cut power:
download → power loss
verification → power loss
flash write → power loss
metadata write → power loss
reboot → power loss
first boot → power loss
health check → power loss
After every test, the device should either:
old firmware works
OR
new firmware works
—not:
device is permanently bricked
Atomic state transitions and redundant metadata are particularly important here. AWS explicitly calls out network errors, corruption and power/reliability failures as OTA concerns.
This is arguably more important than the download infrastructure.
Don't put the production firmware private key:
❌ in Git
❌ in CI environment variables
❌ on developer laptops
❌ inside the device
Prefer:
CI build
│
▼
artifact
│
▼
controlled signing service / HSM
│
▼
signed release
And consider separate keys for:
development
staging
production
TUF is particularly useful if you need sophisticated key delegation and resilience to individual key compromise.
A combination such as:
is a good starting point.
Use a purpose-built system such as:
rather than writing your own boot/update infrastructure from scratch.
If your fleet already uses AWS IoT, AWS IoT Jobs + OTA facilities can provide much of the fleet deployment layer. AWS's FreeRTOS OTA workflow, for example, handles signed-image verification, version checking and deployment orchestration.
Look at TUF/Uptane-style architecture, particularly when you have multiple ECUs, delegated authorities, strict rollback protection and a substantial supply-chain threat model. Uptane is specifically an OTA security architecture derived from TUF.
If I were starting a new connected MCU product today, I'd aim for:
┌───────────────┐
│ CI/CD │
│ build + test │
└───────┬───────┘
│
▼
┌───────────────┐
│ Signing/HSM │
└───────┬───────┘
│
▼
┌──────────────────────┐
│ OTA repository │
│ │
│ signed manifest │
│ firmware artifacts │
│ rollout configuration│
└──────────┬───────────┘
│
HTTPS / MQTT
│
┌──────────────┴──────────────┐
▼ ▼
┌─────────┐ ┌─────────┐
│ Device │ ... │ Device │
│ OTA │ │ OTA │
│ agent │ │ agent │
└────┬────┘ └────┬────┘
│ │
┌────▼─────┐ ┌────▼─────┐
│ Slot A │ │ Slot A │
│ Slot B │ │ Slot B │
└────┬─────┘ └────┬─────┘
│ │
┌────▼─────┐ ┌────▼─────┐
│ Secure │ │ Secure │
│ bootloader│ │ bootloader│
└──────────┘ └──────────┘
The key design principle: make an OTA failure equivalent to “the device continues running the previous known-good firmware.” If you achieve that, then networking, servers, power interruptions and even bad releases become recoverable operational problems rather than truck-rolls.
For further implementation details, the TUF documentation and MCUboot documentation are good starting points.
Implementing an over-the-air (OTA) firmware update system for a fleet of embedded devices requires a robust architecture divided into device-side components, backend infrastructure, and secure transport protocols.
Here is a structured breakdown of how to build and implement an OTA pipeline:
If you'd like, tell me:
I can provide a more tailored architecture or code pattern for your specific hardware stack.
Implementing over-the-air (OTA) firmware updates for a fleet of embedded devices requires a robust architecture spanning the device, communication protocol, and backend cloud infrastructure.
At the core of a reliable OTA system is a secure, fault-tolerant bootloader.
Moving data efficiently and securely from the cloud to the edge.
Managing the lifecycle, targeting, and monitoring of thousands of devices.
Ensuring devices don't become "bricks" in the field.
If you'd like to dive deeper, let me know:
I can tailor a more specific architecture or recommend tools like Mender or AWS IoT Device Management.
A robust OTA system is best designed as four cooperating layers:
A good reference architecture looks like:
Developer
│
▼
CI/CD ──► Build ──► Test ──► Sign ──► Firmware repository
│
▼
OTA orchestrator
/ | \
▼ ▼ ▼
Device Device Device
│
download firmware
│
▼
┌─────────────────┐
│ Bootloader │
│ verify signature │
│ install image │
│ rollback if bad │
└────────┬────────┘
▼
New firmware
For an MCU, use a secure bootloader such as MCUboot. The typical layout is:
Flash
┌─────────────────────────┐
│ Bootloader │
├─────────────────────────┤
│ Application Slot A │ ← currently running
├─────────────────────────┤
│ Application Slot B │ ← downloaded update
├─────────────────────────┤
│ Persistent/config data │
└─────────────────────────┘
The device downloads the new image into the inactive slot. The bootloader verifies it, boots it in test mode, and the new application marks itself healthy only after successful initialization. If it crashes or fails to confirm itself, the bootloader reverts to the previous image. MCUboot explicitly supports this test/confirm/revert model.
For Linux-class devices, an A/B root filesystem is the analogous approach; systems such as Mender use this model for fail-safe updates.
Don't rely on HTTPS/TLS alone.
Your update should contain something conceptually like:
Firmware image
├── magic
├── product/hardware ID
├── firmware version
├── security counter
├── image hash
└── digital signature
The device should verify:
This protects against both tampered firmware and malicious downgrades. MCUboot supports signed images and downgrade/rollback protection mechanisms.
Keep the private signing key out of the firmware repository. Ideally, production signing happens through a protected signing service/HSM with tightly controlled CI permissions.
The OTA agent should roughly implement:
receive update notification
│
▼
check compatibility
│
▼
download to inactive slot
│
▼
verify hash/signature
│
▼
mark image pending
│
▼
reboot
│
▼
bootloader validates image
│
▼
boot new firmware
│
▼
run health checks
│
┌────┴────┐
│ │
healthy failure
│ │
confirm reboot
│ │
▼ ▼
permanent rollback
Make downloads resumable if devices have unreliable connectivity. Also use atomic state transitions so losing power at any point doesn't leave the device unbootable.
Each device should have a unique cryptographic identity, preferably provisioned during manufacturing.
For example:
device_id: 8f31...
hardware: sensor-v3
current_fw: 2.4.1
bootloader: 1.7.0
update_channel: stable
Use mutually authenticated TLS where practical, and authorize devices individually or through tightly controlled groups.
Don't make the device's authorization depend solely on a shared fleet-wide secret.
Instead of simply telling a device "download this binary," publish a signed manifest:
{
"product": "sensor-v3",
"version": "2.5.0",
"security_counter": 17,
"size": 524288,
"sha256": "...",
"url": "...",
"mandatory": false
}
The device checks the manifest before downloading and checks the firmware again afterward.
You can additionally specify:
minimum_bootloader
hardware_revision
release_channel
dependencies
release_notes
This becomes particularly important when you have multiple hardware revisions in the field.
Never push a new firmware version to 100% of the fleet immediately.
A safer progression is:
Internal test devices
↓
1% canary
↓
5%
↓
25%
↓
50%
↓
100%
At every stage monitor:
Automatically pause/abort the rollout when metrics exceed thresholds.
Fleet-management systems such as AWS IoT Jobs provide mechanisms for targeting device groups and tracking individual job execution; AWS specifically recommends controlled and reversible OTA deployments.
You don't necessarily need to build the fleet-management layer yourself.
For example:
For example, an AWS-oriented implementation can be:
CI/CD
│
├── build
├── unit/integration tests
├── sign firmware
│
▼
S3 / firmware storage
│
▼
AWS IoT Jobs
│
├── canary group
├── production group
└── rollback/abort policy
│
▼
Device OTA Agent
│
▼
MCUboot
│
▼
A/B firmware
AWS's OTA tooling supports signed firmware, HTTP/MQTT transfer, and IoT Jobs-based deployment.
This is often overlooked.
You need a secure process for:
Manufacturing
│
├── unique device identity
├── device credentials
├── trusted firmware public key
└── initial firmware
And separately:
Release engineering
│
├── source control
├── reproducible build
├── security scanning
├── tests
├── artifact generation
└── protected signing operation
Consider how you'll rotate signing keys if a key is compromised. Ideally, the bootloader supports a controlled trust-anchor/key-rotation mechanism rather than permanently embedding one immutable key with no recovery path.
OTA isn't production-ready until you've deliberately tested:
The most important property is:
A failed OTA operation must leave the device either running the old known-good firmware or the new known-good firmware—not neither.
If you're building a new MCU fleet, I'd strongly consider:
MCU + Zephyr → MCUboot → A/B firmware slots → HTTPS/MQTT OTA agent → signed firmware → cloud fleet orchestrator → staged deployments + telemetry.
For a Linux embedded device:
Linux → A/B root filesystem → Mender or RAUC/OSTree-style updater → signed artifacts → cloud deployment service → staged rollout.
The exact choice depends heavily on your hardware, RTOS/OS, flash/RAM constraints, connectivity, and fleet size. If you tell me the MCU/SoC (e.g. ESP32, STM32, nRF52/nRF91), RTOS/Linux, connectivity (Wi-Fi/LTE/BLE), and approximate fleet size, I can lay out a concrete OTA architecture and implementation path for it.
Implementing over-the-air (OTA) firmware updates for a fleet of embedded devices requires a robust, secure, and fault-tolerant architecture divided into device-side and cloud-side components.
Step-by-Step Implementation Flow
Build and Sign Firmware:
Publish to Cloud:
Trigger Notification / Poll:
Secure Download:
Cryptographic Verification:
Atomic Swap and Reboot:
Post-Update Health Check:
Essential Security & Fleet Management Practices
To help tailor this architecture, tell me:
For a fleet, treat OTA as a secure deployment system, not merely “download a binary and flash it.” A robust design has four layers:
Cloud / OTA service
│
┌─────────────┴─────────────┐
│ │
Firmware registry Deployment manager
- signed images - cohorts
- versions - rollout %
- metadata - health gates
│ │
└─────────────┬─────────────┘
│ TLS
▼
┌──────────────┐
│ OTA Agent │
│ on device │
└──────┬───────┘
│
download / verify
│
┌──────▼──────┐
│ Inactive │
│ firmware │
│ slot (B) │
└──────┬──────┘
│
bootloader
verify → boot B
│
health check
┌──────┴──────┐
│ │
success failure
│ │
confirm rollback
│ │
▼ ▼
slot B slot A
For an MCU, use an A/B firmware layout:
This is the most important protection against a device becoming permanently bricked. MCUboot, for example, explicitly supports test boots and reverting to the previous image when the new firmware isn't confirmed.
You'll need enough nonvolatile storage for two images, or an equivalent scheme such as a temporary external-flash staging area.
Don't rely on TLS alone.
Your release pipeline should look approximately like:
source
↓
build reproducibly
↓
firmware.bin
↓
generate manifest
↓
sign image with release/private signing key
↓
publish image + metadata
The device bootloader should verify the signature using a public key anchored in trusted storage before allowing the image to execute.
The manifest might contain:
{
"product": "sensor-v2",
"version": "4.7.2",
"hardware_revision": "B",
"image_size": 524288,
"sha256": "...",
"security_counter": 17,
"min_bootloader": "2.1.0"
}
The hash protects integrity; the digital signature establishes that the firmware came from an authorized signer.
Also implement anti-rollback protection. A compromised server shouldn't be able to convince a device to install an old vulnerable image. MCUboot supports security counters specifically for this purpose.
Use TLS for the device ↔ server connection, ideally with per-device credentials/certificates.
But think of the security boundaries separately:
This means an attacker who compromises your CDN or update server still can't simply replace a firmware binary with an unsigned one.
The application/OTA agent should handle:
The bootloader should handle the security-critical decision of whether that image is allowed to execute.
Don't make the application responsible for deciding whether an untrusted image is safe to boot.
Assume all of these will happen:
Your update state machine should therefore be something like:
IDLE
│
├── update available
▼
DOWNLOADING
│
├── interrupted ──→ resume
│
▼
DOWNLOADED
│
├── hash/signature failure ──→ discard
│
▼
VERIFIED
│
▼
PENDING_BOOT
│
▼
REBOOT
│
▼
SELF_TEST
│
├── failure ──→ ROLLBACK
│
▼
CONFIRM
│
▼
RUNNING
Never overwrite your only known-good firmware image unless you have another reliable recovery mechanism.
“Firmware booted” isn't sufficient.
After booting the candidate image, give it a confirmation window—for example:
boot candidate
↓
initialize hardware
↓
run integrity/self-tests
↓
start essential services
↓
establish cloud connection
↓
send "healthy" heartbeat
↓
confirm image
If any critical condition fails, reboot without confirming the image. The bootloader can then revert to the previous image. This is exactly the sort of rollback capability recommended for production IoT deployments.
Don't send version 4.7.2 to 100,000 devices simultaneously.
Use staged deployment:
4.7.2 release
│
├── internal test devices
│
├── 1% canary
│
├── 5%
│
├── 20%
│
├── 50%
│
└── 100%
At every stage monitor:
Automatically stop the rollout if those metrics cross predefined thresholds.
Fleet OTA platforms such as AWS IoT Device Management provide concepts such as deployment groups, rollout velocity, failure thresholds, and continuous jobs for this purpose.
Keep track of at least:
device_id
hardware_revision
bootloader_version
firmware_version
configuration_version
security_counter
update_status
last_update_attempt
last_successful_update
rollback_count
This becomes extremely useful when, for example, you discover that only hardware revision C devices are failing to update.
Initially, full-image updates are much simpler and safer.
If your firmware is 2 MB and you have inexpensive connectivity, start there.
Once bandwidth becomes a significant cost, consider binary deltas:
firmware 4.7.1
+
delta
↓
firmware 4.7.2
But make sure the resulting image is still cryptographically verified as a complete image before execution. Don't let delta-update complexity undermine your recovery model.
This is frequently more important than the OTA protocol itself.
Ideally:
Developer
│
▼
CI build
│
▼
artifact repository
│
▼
controlled release approval
│
▼
hardware-backed signing key
│
▼
signed firmware
│
▼
OTA service
Don't put your production firmware signing private key in a developer laptop or ordinary CI secret.
Consider key rotation, multiple signing keys, revocation/recovery procedures, and a secure bootloader key hierarchy.
For an MCU-based product, a good starting architecture would be:
| Layer | Example approach |
|---|---|
| Bootloader | MCUboot |
| Firmware security | Signed images + secure boot |
| Recovery | A/B + automatic rollback |
| Transport | HTTPS or MQTT over TLS |
| Device identity | Per-device credentials/certificates |
| Update protocol | Resumable chunked download |
| Fleet control | OTA jobs + device cohorts |
| Monitoring | Device heartbeat + update telemetry |
| Release | CI/CD → signing → artifact repository |
| Rollout | Canary → staged deployment → automatic halt |
MCUboot is particularly useful because it is OS-independent and supports several embedded ecosystems.
If you're using embedded Linux rather than an MCU, the same principles apply, but I'd generally use an A/B root filesystem/update framework rather than implementing the flash-management logic yourself.
The key design principle is:
At every point during an OTA operation, the device must have a deterministic path back to known-good firmware.
If you tell me your MCU/SoC (e.g. STM32, ESP32, nRF52/nRF53, NXP, Raspberry Pi), connectivity (Wi-Fi/cellular/BLE), and approximate fleet size, I can map this into a concrete architecture and update state machine for your hardware.