What IoTOps is
Link to section “What IoTOps is”In 30 seconds
Link to section “In 30 seconds”IoTOps (IoT operations) is the work of running fleets of networked devices that have little or no user interface (sensors, meters, locks, cameras, controllers) from the factory to the scrap bin: giving each one an identity, onboarding it, keeping its firmware updatable without bricking it, watching its health within battery and bandwidth limits, and decommissioning it. The IETF’s IOT Operations working group describes the devices well: they are networked, “have a very limited end-user interface or no end-user interface at all”, and are deployed in numbers that “cannot easily be managed or maintained manually”.[1]
In one line: every device in the field is known, reachable on its own terms, safely updatable and deliberately retired.
At a glance
Link to section “At a glance”| Question | Answer |
|---|---|
| Goal | A fleet that is secure, updatable and observable for its whole service life, at a known cost per device |
| Users | Firmware, device-platform, field-service, security and product teams |
| Scope on this page | Microcontroller and single-purpose embedded devices: identity, messaging, state, firmware updates, power budgets, health, decommissioning |
| Out of scope | Gateways and site compute → EdgeOps; laptops and phones → EndpointOps; robots → RoboOps; vehicles → FleetOps; analytics pipelines → DataOps |
| Prerequisites | None. Basic networking and TLS help |
| Key outputs | Identity scheme, rollout policy, fleet KPIs, published support period, decommissioning runbook |
| Term status | Niche label for an established practice (see What IoTOps is not) |
The problem it solves
Link to section “The problem it solves”Three failures recur when nobody runs a device fleet as a system:
- Devices that cannot be fixed. RFC 9019 calls a firmware update mechanism “a fundamental requirement for fixing vulnerabilities”.[2] The Mirai botnet showed the cost of insecure defaults: it scanned Telnet ports 23 and 2323, tried a dictionary of 62 username and password pairs, and peaked at about 600,000 infected devices.[3]
- Updates that break the fleet. In August 2017 LockState told The Register that firmware for its 7i lock “had mistakenly been sent to some 6000i customers”. The locks lost their connection to the vendor’s servers,[4] and at least 500 owners had to mail part of the lock back or wait for a replacement.[5]
- Devices that outlive their dependencies. Google gave Cloud IoT Core customers a year’s notice that from 16 August 2023 devices would be unable to connect to its bridges;[6][7] Let’s Encrypt warned that some older devices “won’t ever trust” its new root “because they don’t get software updates”;[8] US alarms faced the 2022 3G shutdowns.[9]
| Without device operations | With them |
|---|---|
| Shared factory credentials, flashed by hand | One identity per device |
| “Update” means a technician with a cable | Signed, staged, abortable over-the-air (OTA) updates with rollback |
| Silent devices are invisible | Each device has an expected check-in interval; silence is an alert |
| Battery life found out in the field | Energy and message budgets set before release |
| Support ends when the vendor says so | A published support period and a planned end of life |
What IoTOps is not
Link to section “What IoTOps is not”- Not a defined discipline. The IETF uses “IOT Operations” (
iotops) as a working-group name, chartered for “device onboarding and lifecycle management”.[1] SecuriThings, a physical-security vendor, promoted “IoTOps” in 2021 as “a practice to operationally manage IoT devices in a consolidated, automated, and secure manner”;[10][11] in October 2026 its home page says “Enterprise IoT Orchestration” and does not use the word.[12] - Not Azure IoT Operations. That is a Microsoft product, “a unified data plane for the edge” on Arc-enabled Kubernetes,[13]
managed with
az iot ops.[14] Other collisions (as of October 2026): Cisco IoT Operations Dashboard, a cloud service that provisions and manages industrial routers and gateways (EdgeOps territory);[15] a monitoring service sold as “IoT-OPS”;[16] and a 2021 article that expands IoTOps as “IoT operating systems”.[17] - Not edge computing. The IETF’s draft update of RFC 7228 separates “M-group” (microcontroller) from “J-group” (general-purpose, Linux-capable) devices.[18] This page refines the Atlas’s device-class split (an editorial distinction): microcontroller and single-purpose embedded Linux devices (cameras, access controllers) belong here; gateways and general-purpose nodes that host workloads belong to EdgeOps.
- Not user devices, robots or vehicles: EndpointOps, RoboOps, FleetOps.
- Not OT security engineering. Operational technology (OT) such as industrial control systems has its own guidance (NIST SP 800-82, ISA/IEC 62443);[19][20] the Atlas has no OT page, so this page covers device security baselines and SecOps routes the rest.
Where IoTOps comes from and where it sits
Link to section “Where IoTOps comes from and where it sits”Origins and evolution
Link to section “Origins and evolution”- Constrained-node engineering (2014). RFC 7228 classified devices by memory and energy, and the Constrained Application Protocol (CoAP) gave Class 1 devices a web protocol over UDP.[21][22] MQTT 3.1.1 became an OASIS Standard in October 2014 and MQTT 5.0 in March 2019.[23]
- Cloud device platforms. Managed brokers added stored device state (“shadow” on AWS, “twin” on Azure), bulk provisioning and OTA job services.[24][25][26][27]
- Security baselines after Mirai. NIST published its IoT device core baseline in May 2020;[28] ETSI EN 303 645 set consumer baselines (version 3.1.3, September 2024).[29] In April 2026 NIST’s IR 8259 Rev. 1 extended manufacturer guidance to “pre-market through post-market activities”, including end of life.[30][31]
- Regulation. The UK’s Product Security and Telecommunications Infrastructure (PSTI) regime for connectable consumer products took effect on 29 April 2024;[32] the EU Cyber Resilience Act (CRA) entered into force on 10 December 2024, with reporting duties from 11 September 2026 and the main obligations from 11 December 2027.[33]
Glossary and mental model
Link to section “Glossary and mental model”| Term | Meaning | Why it matters |
|---|---|---|
| Constrained device | A node whose limits make “optimization of energy and network bandwidth usage a dominating consideration”;[21] Class 0 (under 10 KiB RAM) to Class 2 (about 50 KiB) | Sets which protocols, crypto and updates fit |
| Provisioning / onboarding | Giving a device its identity and credentials, then connecting it to its owner’s service | A leaked provisioning key compromises a batch |
| Shadow / twin | Cloud copy of a device’s desired and reported state; the difference is the delta[34][25] | Lets apps change a sleeping device’s configuration |
| Telemetry | Time series sent as messages, not stored in the shadow | Mixing them inflates cost, hides history |
| Job / OTA campaign | A change sent to many devices, with rollout rate, timeouts, abort criteria[27] | The blast-radius control |
| A/B slots; test, confirm, revert | Two firmware images; the new one boots as a test and becomes permanent only when confirmed[35] | The difference between rollback and a brick |
| Manifest | Signed metadata for an image: vendor, device class, sequence number[36] | Stops wrong-model and downgrade installs |
| Last Will / heartbeat | Message the broker publishes when a client vanishes;[23] a device’s own periodic status | How you know who is silent |
| Support period | The time for which the manufacturer handles vulnerabilities and ships security updates[37] | Published under UK law today; regulated by the CRA from December 2027 |
| CA, CN, CSR, CRL | Certificate authority; common name (the identity field of a certificate); certificate signing request; certificate revocation list | The vocabulary of per-device identity |
Mental model: three planes and one budget. The identity plane answers who a device is and what it may do. The state plane holds desired versus reported configuration. The change plane moves firmware and settings through staged jobs. All three are limited by one budget: energy, airtime and money. Every operation is asynchronous: the device, not the operator, decides when it is awake.
How IoTOps relates to other Ops
Link to section “How IoTOps relates to other Ops”Diagram source (Mermaid)
flowchart TB SE["SecOps: baselines, OT routing"]:::neutral -->|"security baseline"| I PR["PrivacyOps: legal basis"]:::neutral -->|"data rules"| I I["IoTOps: constrained device fleet"]:::core I -->|"gateways, site nodes"| ED["EdgeOps"]:::neutral I -->|"health signals"| OB["ObservabilityOps"]:::neutral I -->|"device data"| DA["DataOps"]:::neutral I -->|"telematics"| FL["FleetOps"]:::neutral I -->|"device patterns: identity, OTA"| RO["RoboOps"]:::neutral classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
Text equivalent: SecOps supplies security baselines and routes OT questions, and PrivacyOps supplies data rules, to IoTOps, which runs the constrained device fleet. IoTOps hands gateways and site nodes to EdgeOps, health signals to ObservabilityOps, device data to DataOps and telematics to FleetOps, and shares its device patterns (identity, OTA updates) with RoboOps.
| Neighbour | This page | The neighbour |
|---|---|---|
| EdgeOps | Microcontroller and single-purpose embedded devices | Gateways and general-purpose nodes that host workloads |
| PatchOps, ReleaseOps | Firmware delivery under battery, airtime and bricking risk | Patch cycles and rings; release strategy in general |
| IdentityOps, IAMOps | Per-device certificates and topic permissions | Human and workload identity; policy design |
| ObservabilityOps, IncidentOps | Fleet health signals and device runbooks | Telemetry pipelines; incident command |
| CloudOps, FinOps | Device-platform quotas and per-device cost | Cloud control plane; cost practice |
Principles and operating model
Link to section “Principles and operating model”Charter and principles
Link to section “Charter and principles”Charter (this Atlas’s proposal, not a published standard): keep every device known, reachable, updatable and retirable for its support period, at an accepted cost per device.
- Design for the device you cannot reach. AWS’s IoT Lens says: “Design for offline behavior” and track devices “that are not connected or communicating on a regular timeframe”.[38]
- No update path, no shipment. An update mechanism goes in before the first unit leaves the factory.[2]
- One identity per device, never shared, even across a test batch.
- Every fleet change is staged and abortable, with a written abort rule.
- Confirm only after a self-test that includes reconnecting.
- Spend energy and airtime on purpose; every reporting interval is a budget decision.
- Publish the support period and plan the end before launch.
- Count the silent devices: fleet KPIs divide by the devices you expect to hear from.
Non-goals: analytics on device data, OT process safety, and user-device management.
Fundamentals: the building blocks
Link to section “Fundamentals: the building blocks”| Building block | Job | Standards and open options | Managed examples (October 2026) | Typical failure |
|---|---|---|---|---|
| Identity and provisioning | Unique credentials; first connection | X.509, BRSKI (bootstrapping with manufacturer certificates),[39] FIDO Device Onboard (FDO)[40] | AWS fleet provisioning,[41] Azure Device Provisioning Service (DPS)[26] | Leaked claim key; shared certificate |
| Messaging | Telemetry and commands | MQTT,[23] CoAP,[22] OMA Lightweight M2M (LwM2M)[42] | AWS IoT Core, Azure IoT Hub | Wrong quality-of-service (QoS) level; no Last Will |
| State sync | Desired versus reported | Eclipse Ditto twins[43] | Device Shadow,[24] device twin[25] | Telemetry stored as state |
| Change | Jobs, OTA, bootloader | MCUboot,[35] Mender,[44] RAUC,[45] hawkBit[46] | AWS IoT Jobs,[27] Device Update for IoT Hub[47] | No class check; no revert |
| Health and audit | Check-ins, fleet audit | MQTT Last Will[23] | AWS IoT Device Defender audits[48] | Silent devices unnoticed |
| Network control | Limit what a device may reach | Manufacturer Usage Description (MUD)[49] | Gateway segmentation[38] | Flat network |
Connectivity shapes everything. LoRaWAN Class A devices receive downlinks only in two short windows after they transmit, and must switch to Class B or C for firmware updates over the air;[50] European sub-bands typically limit transmit time to 1% or 0.1%.[51] Cellular LTE-M and NB-IoT devices use power saving mode (PSM), registered but not pageable while asleep, or extended discontinuous reception (eDRX), which trades some battery for reachability; the network sets or negotiates several timers.[52]
Roles, ownership and RACI
Link to section “Roles, ownership and RACI”| Activity | Product owner | Firmware team | Device platform (IoTOps) | Security | Field service |
|---|---|---|---|---|---|
| Set and publish the support period | A | C | C | C | I |
| Identity scheme and CA | I | C | R | A | I |
| Build and sign firmware | I | R/A | C | C | I |
| Run rollouts and abort | I | C | R/A | C | I |
| Handle an exploited vulnerability | A | R | R | R | I |
| Field replacement after a failed update | C | I | A | I | R |
| Decommission and transfer | A | I | R | C | R |
R = responsible, A = accountable, C = consulted, I = informed. In a small company two people hold every column; the RACI still names who halts a rollout at 03:00.
Lifecycle of a device
Link to section “Lifecycle of a device”Diagram source (Mermaid)
stateDiagram-v2 [*] --> Manufactured Manufactured --> Provisioned: identity injected Provisioned --> Commissioned: installed, onboarded Commissioned --> Operational Operational --> Updating: update job Updating --> Operational: confirmed or reverted Operational --> Quarantined: fault Quarantined --> Operational: fixed Operational --> Decommissioned: retired Quarantined --> Decommissioned: not recoverable Decommissioned --> Provisioned: new owner Decommissioned --> [*]
Text equivalent: a device is manufactured, provisioned with an identity, then installed and onboarded. In operation it loops through updates, which end either confirmed or reverted. A faulty or compromised device is quarantined, then fixed or decommissioned. A decommissioned device is either re-provisioned for a new owner or disposed of.
RFC 8576 adds that a device may be “removed and recommissioned to be used in a different system under a different owner”, and lists uncomfortable end states: a vendor that “no longer supports a device type”, or a device “simply forgotten but remains functional”.[53]
- Day 0 (design): hardware class, bootloader and slots, secure key storage, identity scheme, energy budget, support period.
- Day 1 (rollout): factory provisioning, onboarding at the site, first check-in, inclusion in fleet KPIs.
- Day 2 (operation): updates, credential rotation, health review, quarantine, field service, decommissioning.
Operating model and governance
Link to section “Operating model and governance”| Cadence | Forum | Inputs | Decisions |
|---|---|---|---|
| Weekly | Fleet health review | Reachability, battery forecast, failed jobs | Field visits; rollout go or halt |
| Per release | Rollout review | Tests, canary plan, abort rules | Approve waves |
| Monthly | Identity and audit review | Expiring certificates, audit findings | Rotation jobs, revocations |
| Quarterly | Lifecycle review | Support periods, network and platform notices | End-of-life plans, migrations |
| On trigger | Vulnerability handling | Reports, exploitation evidence | Emergency rollout; regulator notice |
Fleet-wide destructive actions (factory reset, batch revocation, forced job cancellation) need a second approver.
Architecture and workflow
Link to section “Architecture and workflow”Reference architecture
Link to section “Reference architecture”Diagram source (Mermaid)
flowchart TB
subgraph DEV["Device"]
KS["Key store"]:::core
BL["Bootloader, slots A/B"]:::core
AG["Device agent"]:::core
end
KS -->|"signs TLS handshake"| AG
BL -->|"verifies, boots image"| AG
CA["CA and provisioning"]:::core
AG --> LK["Radio link or gateway"]:::neutral
CA --> BR
LK -->|"mutual TLS"| BR["Broker"]:::core
BR --> Q["Ingest queue"]:::neutral
BR --> SH["Shadow or twin"]:::core
BR --> JB["Jobs and image store"]:::core
LK -->|"image download"| JB
Q --> TS["Time-series store"]:::neutral
BR -->|"presence events"| FI["Fleet index and health"]:::decision
SH --> FI
JB --> FI
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
Text equivalent: on the device, a key store signs the TLS handshake and a bootloader with two firmware slots verifies and boots the image that runs the device agent. The agent reaches the platform through a radio link or a gateway: it connects to the broker with mutual TLS and downloads firmware images over HTTPS from the job service’s image store, outside the broker, with a time-limited address. The certificate authority and provisioning service registers devices with the broker, which feeds the shadow or twin store, the job service and an ingest queue into a time-series store. A fleet index tracks device health from the broker’s presence events (connect, disconnect, Last Will), the shadow store and the job service.
| Component | Responsibility | Trust boundary and notes |
|---|---|---|
| Bootloader and slots | Verify the image signature; boot a test image; revert if it is not confirmed[35] | The bootloader itself is rarely updated, because a failed bootloader update “poses a reliability risk”[2] |
| Key store | Hold the private key, ideally generated on the device and never exported | Debug, serial and USB ports can reach it: NIST lists “Logical Access to Interfaces” as a core capability[28] |
| CA and provisioning | Issue or register device certificates; map devices to tenants and hubs[26][41] | CA and firmware-signing keys are the most valuable secrets here |
| Broker | Authenticate, authorise per topic, deliver messages, publish Last Will[23] | Per-device policy (see the reference implementation) |
| Shadow or twin | Desired and reported state, versioned[34] | Not for time series[25] |
| Jobs and image store | Rollout rate, abort rules, timeouts; time-limited download URLs[27][54] | Images signed offline; served only against an unexpired URL |
| Ingest queue, fleet index | Decouple telemetry from processing;[38] last seen (from connect, disconnect and Last Will events[55]), firmware, battery, job state per device | Pipelines with DataOps; the index feeds every KPI |
Alternatives. A managed platform (AWS IoT Core, Azure IoT Hub) removes broker operations but ties identity, state and jobs to one provider; a self-hosted stack (Mosquitto, hawkBit, Ditto or ThingsBoard)[56][46][43][57] keeps control and adds work; an LwM2M server offers a standard device-management object model.[42] Gateways for Class 0 devices[21] and LoRaWAN network servers become part of the platform you operate.
End-to-end workflow: one firmware release
Link to section “End-to-end workflow: one firmware release”Diagram source (Mermaid)
sequenceDiagram participant Op as Operator participant Svc as Job service participant Dev as Device agent participant Boot as Bootloader Op->>Svc: Create job for class rev A Svc->>Dev: Job notice at next check-in Dev->>Svc: Get job document Svc-->>Dev: Document with time-limited URL Note over Dev: Fetch image from store Dev->>Boot: Image in slot B, marked test Boot-->>Dev: Boots slot B Dev->>Svc: Self-test passed, reconnected Dev->>Boot: Confirm image Dev->>Svc: Report SUCCEEDED Svc-->>Op: Wave statistics
Text equivalent: the operator creates a job for hardware class rev A. The job service notifies the device when it next checks in; the
device asks for the job document and receives it with a time-limited download address, then downloads the signed image from the
image store. The device writes the image to slot B and marks it as a test; the bootloader boots it. After its self-test passes and it
has reconnected to the job service, the device confirms the image and reports SUCCEEDED. The job service reports wave statistics
to the operator.
Happy path: the figure, when class, battery, signature and sequence number all pass.
Exception paths.
- Rejected before install: wrong class, bad signature or an older sequence number. RFC 9124 requires a monotonic sequence number
“to prevent malicious actors from reverting a firmware update” and a Class ID for devices that “can accept the same firmware
update without modification”.[36] The device reports
REJECTED, which AWS IoT Jobs can count towards an abort.[27] - Crash or hang after install: the unconfirmed image reverts at the next reset,[35] and the device reports failure from the old image.
- Boots but cannot reconnect: the most dangerous case, because a device that confirms is then unreachable. Hence the self-test includes a round trip to the service (an editorial recommendation built on MCUboot’s “self-test routine” guidance).[35]
- Silent: an
IN_PROGRESSexecution that goes quiet becomesTIMED_OUTwhen the timeout expires;[58] a device that never fetched the job stays queued. Count both until proven otherwise.
Practices, standards and tools
Link to section “Practices, standards and tools”Core practices and playbooks
Link to section “Core practices and playbooks”| Practice | Goal | Trigger | Owner | Output | Verification |
|---|---|---|---|---|---|
| Per-device identity | Nothing shared | Manufacturing, first boot | Device platform | Certificate per device | Shared-certificate audit finds none[59] |
| Staged rollout | Bounded blast radius | Every firmware or config change | Device platform | Waves with abort rules | Abort rule fired in a drill |
| Desired-state configuration | Change sleeping devices | Configuration change | Device platform | Desired properties | Delta empty after check-in |
| Expected check-in | Detect silence | Always | Device platform | Last-seen per device | Silent-device count reviewed weekly |
| Energy and message budget | Battery and bill as designed | New firmware, new interval | Firmware team | Budget sheet (Lab 1) | Field battery trend matches forecast |
| Credential rotation | No surprise expiry | Expiry within threshold | Security, platform | New certificate in use, old one retired (playbook) | Expiring-certificate audit[60] |
| Decommissioning | Nothing left trusted | Retirement, resale | Platform, field | Revoked identity, wiped device | Device absent from registry and allow-lists |
Playbook: provisioning and identity
Link to section “Playbook: provisioning and identity”- Generate the key where it will live (ideally a secure element); the CA signs the device’s certificate signing request (CSR).
- Choose an onboarding pattern: certificates registered at manufacture; by claim (a shared claim certificate is exchanged for a unique one at first connection); by trusted user (an installer’s app gets a claim certificate valid for five minutes);[41] or late binding with FDO ownership vouchers.[40] Azure’s DPS attests X.509, TPM or symmetric-key identities.[26]
- Treat claim credentials as hazardous. AWS says claim private keys “should be secured at all times, including on the device”, and to turn off the claim certificate if misuse is detected.[41]
- Bind permissions to the identity. The device may use only its own client ID and topics (see the reference implementation).
- Plan trust-store updates. Roots expire too: DST Root CA X3, which cross-signed Let’s Encrypt’s chain, expired on 30 September 2021; IoT clients needed Let’s Encrypt’s ISRG Root X1 and, with OpenSSL, version 1.1.0 or later.[8] Ship spare trust anchors and update them over the air.
Playbook: rotating a device certificate
Link to section “Playbook: rotating a device certificate”Adapted from AWS’s reference flow;[61] the rule is the firmware rule: test, then confirm.
- Start early: an expiring-certificate audit (30 days by default) or a schedule opens a rotation job.[60]
- New key on the device, ideally in its secure element (AWS calls key rotation “optional but recommended”); the device sends a CSR over its current session.[61]
- Same rights: the CA signs it (on AWS,
CreateCertificateFromCsr[62]); attach the same policy and the same thing, which theIsAttachedcondition requires.[63] - Store beside, not over: keep the old key and certificate until the new pair has worked.
- Reconnect and report with the new certificate; on failure, reconnect with the old one and report the error.[61]
- Retire the old certificate only after success: deactivate, then revoke or delete it (self-hosted: add it to the CRL).
- Chase stragglers: sleeping or failed devices stay on the audit list and retry. Lab 2 rehearses steps 2–4.
Playbook: safe firmware rollout
Link to section “Playbook: safe firmware rollout”- Pre-flight: signed image; manifest with class and sequence number; the previous image still on the device.
- Eligibility: right class, enough battery, seen recently; the rest are deferred and counted.
- Canary by stratum: devices from each hardware revision, radio type and region, with the abort rule checked per class (one job per class); the first 1% of a sorted list is not a canary (Lab 3).
- Abort rules: in AWS IoT Jobs, abort criteria combine a failure type (
FAILED,REJECTED,TIMED_OUTorALL), a threshold percentage and a minimum number of devices notified before the job can abort.[64] Keep that minimum no larger than the canary. - Rate and time: an exponential rate rises only after a set number of devices were notified or succeeded; maintenance
windows (continuous jobs only) avoid busy hours; an in-progress timeout turns silence into
TIMED_OUT.[27][58] - Confirm after a self-test that includes reconnecting. Mender commits on first boot and rolls back if the device reboots before committing;[44] RAUC warns that a clean write “does not mean that the system you just installed will really boot”.[45]
- Watch the deferred and the silent until the campaign closes.
Playbook: connectivity, battery and reconnection
Link to section “Playbook: connectivity, battery and reconnection”- Commands to sleeping devices go into desired state, not queued messages: AWS keeps persistent MQTT sessions for 3,600 seconds by default,[65] while a shadow holds the desired value until the device next connects.[24]
- Presence: set the MQTT Last Will so the broker announces abnormal disconnects,[23] and send status on a fixed interval, as AWS’s IoT Lens recommends.[38]
- Reconnect with jittered backoff. Devices retrying on one schedule after an outage arrive together; Marc Brooker concludes “The solution isn’t to remove backoff. It’s to add jitter.”[66] AWS IoT Core allows one connect per second per client ID.[65]
- Count the handshake. A sensor that reconnects for every report repeats a TLS or DTLS handshake, certificate chain included, to send a few bytes. RFC 7925’s IoT profile requires constrained clients to support session resumption, which needs less bandwidth, and certificate-chain caching;[67] the DTLS Connection ID avoids a new handshake when a sleeping device’s address changes.[68]
- Keep low-power wide-area network (LPWAN) payloads small and binary, avoid downlinks and unnecessary confirmed uplinks.[69]
Playbook: decommissioning and transfer
Link to section “Playbook: decommissioning and transfer”- Revoke the certificate and remove the device from groups, jobs and network allow-lists. A self-hosted broker needs the updated CRL and a reload; then check that the device is refused when it next connects.[56]
- Wipe credentials and user data; from December 2027 the CRA requires that users can “securely and easily remove on a permanent basis all data and settings”.[70]
- For resale or transfer, re-provision under the new owner (RFC 8576’s “reownership”).[53]
- For a product line, publish the end of support and ship a final update; RFC 8576 argues for an “authorization handover” so owners can add trust anchors and install firmware from other sources.[53]
Methods, standards and frameworks
Link to section “Methods, standards and frameworks”| Source | Kind | What it asks of operations |
|---|---|---|
| UK PSTI regime (since 29 April 2024)[32] | Law, UK consumer connectable products | Unique or user-set passwords; a way to report security issues; a published minimum security update period with an end date |
| EU Cyber Resilience Act[33][70] | Law, products with digital elements in the EU | Since 11 September 2026: reporting exploited vulnerabilities and severe incidents, also for products already sold. From 11 December 2027: a support period of at least five years (shorter only if expected use is), and security updates |
| ETSI EN 303 645 V3.1.3[29] | European standard, consumer IoT | 13 provision groups, from “No universal default passwords” to “Validate input data” |
| NIST IR 8259 Rev. 1, IR 8259A, SP 800-213[30][28][71] | US guidance for manufacturers and buyers | Six device capabilities, including software update and cybersecurity state awareness |
| IETF RFC 9019, RFC 9124, RFC 8520[2][36][49] | IETF RFCs | Update architecture, manifest information, network access profiles |
| ISA/IEC 62443, NIST SP 800-82 Rev. 3 (Rev. 4 in initial public draft, comments due 30 November 2026)[20][19] | Industrial and OT security | Apply when devices control physical processes |
Laws bind the manufacturer placing the product on the market; standards and guidance bind only when a contract, conformity route or regulator adopts them. Check scope with ComplianceOps.
Tooling landscape
Link to section “Tooling landscape”| Job | Open-source examples | Managed examples (as of October 2026) | Selection notes |
|---|---|---|---|
| Broker | Eclipse Mosquitto[56] | AWS IoT Core, Azure IoT Hub | Per-device authorisation, quotas, MQTT 5 support |
| Provisioning | FDO implementations[40] | AWS fleet provisioning, Azure DPS[41][26] | Where keys are born; late binding |
| State and twins | Eclipse Ditto, ThingsBoard[43][57] | Device Shadow, device twins[24][25] | Size limits and metering of state |
| Rollout orchestration | Eclipse hawkBit[46] | AWS IoT Jobs, Device Update for IoT Hub[27][47] | Abort rules, maintenance windows |
| On-device update | MCUboot (microcontrollers: revert unless confirmed); Mender, RAUC (embedded Linux A/B images)[35][44][45] | Device Update: image- or package-based, ThreadX samples; Microsoft says images give more confidence in the end state[47] | Flash for two images; power-loss safety |
| Fleet audit | — | AWS IoT Device Defender[48] | Shared and expiring certificates |
Platform lifecycle is a risk to manage. Microsoft’s Azure Updates notice AZ569914 (29 September 2026) retires Azure IoT Central on 20 September 2029 and points to IoT Hub, DPS and Azure Device Registry;[72] a 2024 retirement message had been withdrawn as an error.[73] Keep identity and update logic portable, with an exit plan.
Automation and AI
Link to section “Automation and AI”Automate the change plane: eligibility filters, abort rules, expiry alerts and rotation jobs belong in code. AI has narrower uses: anomaly detection on fleet health needs a baseline per hardware class and firmware version, and an agent should propose, not push, firmware changes.
IoTOps in practice
Link to section “IoTOps in practice”Reference implementation
Link to section “Reference implementation”Assumptions (illustrative): 10,000 battery-powered LTE-M sensors, two hardware revisions (sensor-revA, sensor-revB), one
AWS account and region. It maps to Azure IoT Hub with DPS and Device Update, or to Mosquitto with hawkBit.
1. Naming and identity. Thing name = MQTT client ID = certificate subject CN, for example sensor-0001. One certificate per
device; thing groups per hardware revision and per rollout wave (sensor-revA-canary, sensor-revA-wave2 …).
2. Least-privilege device policy, after AWS’s “registered devices” example: iot:Connection.Thing.ThingName is resolved from
the certificate the device presents, and IsAttached admits only registered things attached to it.[63][74]
In AWS policies * is not confined to one topic level,[74] so shadow/* also covers named shadows, but only the
device’s own.
Example status: JSON syntax-checked; not executed (requires an AWS account). Replace the region and 123456789012.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "iot:Connect",
"Resource":
"arn:aws:iot:eu-west-1:123456789012:client/${iot:Connection.Thing.ThingName}",
"Condition": { "Bool": { "iot:Connection.Thing.IsAttached": "true" } }
},
{
"Effect": "Allow",
"Action": "iot:Publish",
"Resource": [
"arn:aws:iot:eu-west-1:123456789012:topic/dt/sensor/${iot:Connection.Thing.ThingName}",
"arn:aws:iot:eu-west-1:123456789012:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/*",
"arn:aws:iot:eu-west-1:123456789012:topic/$aws/things/${iot:Connection.Thing.ThingName}/jobs/*"
]
},
{
"Effect": "Allow",
"Action": "iot:Subscribe",
"Resource": [
"arn:aws:iot:eu-west-1:123456789012:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/shadow/*",
"arn:aws:iot:eu-west-1:123456789012:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/jobs/*"
]
},
{
"Effect": "Allow",
"Action": "iot:Receive",
"Resource": [
"arn:aws:iot:eu-west-1:123456789012:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/*",
"arn:aws:iot:eu-west-1:123456789012:topic/$aws/things/${iot:Connection.Thing.ThingName}/jobs/*"
]
}
]
}
Self-hosted equivalent. Mosquitto can require client certificates, use the certificate’s CN as the user name, and substitute
it into access control list (ACL) patterns with %u.[56] Three preconditions make that safe:
- A CA for devices only. Any CA in
cafilecan issue certificates “valid for connecting to your broker”, and any of them can claim any CN.[56] - A CRL. Without
crlfile, a retired device’s unexpired certificate still connects; reload the broker (SIGHUP) after each CRL update.[56] - Read-only commands, with answers on a separate topic, as in the AWS policy, where commands arrive only through shadow and jobs.
Recent Mosquitto documentation suggests the mosquitto_acl_file plugin in place of acl_file; check your version.
Example status: compared line by line with the mosquitto.conf manual; not executed (no broker on the test host).
# mosquitto.conf (excerpt)
listener 8883
# CA that issues device certificates only, and its revocation list
cafile /etc/mosquitto/ca/device-ca.pem
crlfile /etc/mosquitto/ca/device-ca.crl
certfile /etc/mosquitto/certs/broker.pem
keyfile /etc/mosquitto/certs/broker.key
require_certificate true
use_identity_as_username true
acl_file /etc/mosquitto/acl
# /etc/mosquitto/acl : each device may use only topics under its own CN
pattern write dt/sensor/%u
pattern read cmd/sensor/%u/#
pattern write resp/sensor/%u
3. A firmware job with staged rate, abort rules and timeout. The job document’s fields are this design’s own (AWS imposes no schema); the presigned-URL placeholder is AWS’s syntax, filled in when a device asks for the document.[54] Class and sequence number follow RFC 9124, but this is not an IETF SUIT (Software Updates for Internet of Things) manifest.[36]
Example status: JSON syntax-checked; the create-job command passed parameter validation in AWS CLI 1.18.69 with all credentials
disabled (no request sent); not executed against AWS.
{
"operation": "firmware-update",
"hwClass": "sensor-revA",
"version": "2.4.1",
"sequence": 1760745600,
"sha256": "<64-hex-digest-of-the-image>",
"url":
"${aws:iot:s3-presigned-url-v2:https://s3.eu-west-1.amazonaws.com/example-fw-bucket/sensor-revA/2.4.1.bin}",
"minBatteryPct": 30,
"confirmAfter": ["selftest", "cloud-reconnect"]
}
{
"maximumPerMinute": 200,
"exponentialRate": {
"baseRatePerMinute": 10,
"incrementFactor": 2.0,
"rateIncreaseCriteria": { "numberOfSucceededThings": 50 }
}
}
{
"criteriaList": [
{ "failureType": "FAILED", "action": "CANCEL",
"thresholdPercentage": 5, "minNumberOfExecutedThings": 20 },
{ "failureType": "TIMED_OUT", "action": "CANCEL",
"thresholdPercentage": 10, "minNumberOfExecutedThings": 20 }
]
}
# Files above saved as job-document.json, rollout.json, abort.json.
# Canary group: at least 20 devices (minNumberOfExecutedThings), and enough of each
# revision for a one-revision fault to cross the threshold.
aws iot create-job \
--job-id fw-2-4-1-revA-wave1 \
--targets arn:aws:iot:eu-west-1:123456789012:thinggroup/sensor-revA-canary \
--document file://job-document.json \
--target-selection SNAPSHOT \
--presigned-url-config \
roleArn=arn:aws:iam::123456789012:role/iot-fw-download,expiresInSec=3600 \
--job-executions-rollout-config file://rollout.json \
--abort-config file://abort.json \
--timeout-config inProgressTimeoutInMinutes=60
4. Device-side rules. Accept only if hwClass matches, the sequence number is higher than the installed one and the battery
is above minBatteryPct; verify hash and signature; install to the inactive slot as a test image; reboot. Then confirm, as below
for embedded Linux with RAUC, whose rauc status mark-good and mark-bad signal success or failure to the bootloader.[45] The
marker holds the new image’s IMAGE_VERSION, which every image must set,[75] so after a bootloader fallback the old slot
is kept, never marked bad. On Zephyr with MCUboot, boot_write_img_confirmed() marks the running image OK so MCUboot keeps
it.[76]
Example status: syntax-checked; run against stub rauc, systemctl and checks in eight cases, from a clean pass to a fallback
during a cloud outage (bash 5.0, 2026-10-18); not run on a device. selftest-local and cloud-roundtrip stand for your own
checks.
#!/usr/bin/env bash
# confirm-boot.sh: runs once after every boot (e.g. a systemd unit) on a device
# with RAUC-managed A/B slots. Just before it reboots into a newly installed
# slot, the update agent writes that image's IMAGE_VERSION to $PENDING; it reports
# SUCCEEDED after a confirm, or FAILED from the old image after a fallback.
set -u
PENDING=${PENDING:-/data/ota/pending}
if [ ! -e "$PENDING" ]; then # a slot confirmed earlier: keep it good
rauc status mark-good # (backends may count boot attempts)
exit 0
fi
. /etc/os-release # sets IMAGE_VERSION of the running image
want=$(cat "$PENDING")
if [ -z "${IMAGE_VERSION:-}" ] || [ -z "$want" ]; then
exit 1 # unknown image: change nothing
fi
if [ "$IMAGE_VERSION" != "$want" ]; then
# The new slot failed before this ran and the bootloader fell back to the old
# one, which is running now: keep it good, never mark it bad.
rauc status mark-good
rm -f "$PENDING"
exit 0
fi
for attempt in 1 2 3 4 5; do
# local self-test, then connect with the device certificate and get a reply
if selftest-local && cloud-roundtrip; then
rauc status mark-good && rm -f "$PENDING" && exit 0
fi
sleep $((attempt * 60 + RANDOM % 60)) # jittered retries, 15-20 minutes
done
# Not confirmed in time (a long outage lands here too): give up the slot.
rauc status mark-bad # backend disables it or switches slot
rm -f "$PENDING"
systemctl reboot
Hands-on labs
Link to section “Hands-on labs”Lab 1 (beginner): a battery and message budget
Link to section “Lab 1 (beginner): a battery and message budget”Objective: see how sampling, batching and payload size drive battery life, freshness and message cost. Prerequisites: Python 3.8 or later; 15 minutes. Cost and risk: none (arithmetic).
- Save the script as
budget.py. - Run
python3 budget.py. - Run
python3 budget.py --reading-bytes 12 --batches 48,96(binary readings instead of JSON). - Replace the defaults with currents measured on your own board with a power profiler. If the device reconnects for each report,
include the TLS or DTLS handshake in
--tx-s; the byte columns count the message only.
Example status: verified by execution (Python 3.8.10, 2026-10-18); inputs are illustrative assumptions, not measurements of any product. Price: AWS’s Europe (Ireland) example, $1 per million messages (as of October 2026).[77]
#!/usr/bin/env python3
"""Battery, freshness and message budget for a battery-powered sensor fleet.
The device samples every --sample-min minutes and transmits a batch of
readings every k samples. Pure arithmetic: no network, no device access.
Every default is an illustrative assumption; measure your own board.
"""
import argparse
import math
def plan(k, a):
samples = 24 * 60 / a.sample_min # per device per day
reports = samples / k
awake_s = samples * a.sense_s + reports * a.tx_s
mas = (samples * a.sense_s * a.sense_ma + reports * a.tx_s * a.tx_ma
+ (86400 - awake_s) * a.sleep_ua / 1000) # mA-seconds per day
years = a.capacity_mah * (1 - a.derate) / (mas / 3600) / 365
payload_kb = (a.header_bytes + k * a.reading_bytes) / 1024
metered = math.ceil(payload_kb / 5) # 5 KB metering steps
msgs = reports * 30 * a.fleet * metered # fleet, per month
return mas / 86.4, years, payload_kb, metered, msgs
def main():
p = argparse.ArgumentParser()
p.add_argument("--batches", default="1,4,12,48,96", help="samples per report")
p.add_argument("--sample-min", type=float, default=5)
p.add_argument("--capacity-mah", type=float, default=2600)
p.add_argument("--derate", type=float, default=0.30,
help="share of capacity lost to self-discharge, cold, pulses")
p.add_argument("--sleep-ua", type=float, default=5.0)
p.add_argument("--sense-ma", type=float, default=3.0)
p.add_argument("--sense-s", type=float, default=0.2)
p.add_argument("--tx-ma", type=float, default=120.0)
p.add_argument("--tx-s", type=float, default=4.0, help="attach + send")
p.add_argument("--header-bytes", type=int, default=200)
p.add_argument("--reading-bytes", type=int, default=60)
p.add_argument("--fleet", type=int, default=10000)
p.add_argument("--usd-per-million", type=float, default=1.00)
p.add_argument("--target-years", type=float, default=5.0)
a = p.parse_args()
if a.sample_min <= 0:
raise SystemExit("--sample-min must be positive")
print(f"{'k':>3} {'report':>7} {'avg uA':>7} {'years':>6} {'KB':>5} "
f"{'metered':>7} {'msgs/month':>12} {'USD/month':>9} flag")
for k in [int(x) for x in a.batches.split(",")]:
if k < 1:
raise SystemExit("batch sizes must be >= 1")
avg_ua, years, kb, metered, msgs = plan(k, a)
usd = msgs / 1e6 * a.usd_per_million
flag = "BELOW TARGET" if years < a.target_years else ""
print(f"{k:>3} {k * a.sample_min:>5g}m {avg_ua:>7.1f} {years:>6.1f} "
f"{kb:>5.1f} {metered:>7} {msgs:>12,.0f} {usd:>9,.2f} {flag}")
if __name__ == "__main__":
main()
Expected output (step 2, then step 3):
k report avg uA years KB metered msgs/month USD/month flag
1 5m 1606.9 0.1 0.3 1 86,400,000 86.40 BELOW TARGET
4 20m 407.0 0.5 0.4 1 21,600,000 21.60 BELOW TARGET
12 60m 140.3 1.5 0.9 1 7,200,000 7.20 BELOW TARGET
48 240m 40.3 5.2 3.0 1 1,800,000 1.80
96 480m 23.7 8.8 5.8 2 1,800,000 1.80
k report avg uA years KB metered msgs/month USD/month flag
48 240m 40.3 5.2 0.8 1 1,800,000 1.80
96 480m 23.7 8.8 1.3 1 900,000 0.90
Validation: batching 48 readings per report cuts average current from about 1,607 µA to 40 µA, because the radio attaches 6
times a day instead of 288. At 96 readings the JSON payload (5.8 KB) crosses AWS’s 5 KB metering step,[77] so halving the
reports does not halve the bill until readings become 12-byte binary records. The price is freshness: data can be eight hours old,
so alarms need an event-triggered send. Troubleshooting: years far above target usually mean the transmit time omits network
attach; batch sizes must be >= 1 means a zero in --batches. Teardown: delete budget.py.
Lab 2 (intermediate): per-device identity and expiry with a test CA
Link to section “Lab 2 (intermediate): per-device identity and expiry with a test CA”Objective: issue per-device certificates, verify chains, find certificates about to expire, rotate one, and see a root change from the device’s side. Prerequisites: bash and OpenSSL 1.1.1 or later; 10 minutes. Cost and risk: none; throwaway test keys in a temporary directory that the script deletes.
- Save the script as
lab2.shand runbash lab2.sh. - Read the three device lines, the rotation pair, and the two verifications of the broker certificate.
Example status: verified by execution (OpenSSL 1.1.1f, bash 5.0, 2026-10-18); OpenSSL 3 output differs slightly in subject formatting (not executed with 3.x).
#!/usr/bin/env bash
# Lab 2: per-device identity with a private TEST CA. Never use these keys in production.
set -euo pipefail
WORK=$(mktemp -d) # everything stays in this directory
cd "$WORK"
echo "work dir: $WORK"
# 1. A test CA (stands in for your device-issuing CA or a cloud-registered CA).
openssl ecparam -name prime256v1 -genkey -noout -out ca.key
openssl req -x509 -new -key ca.key -sha256 -days 3650 \
-subj "/O=Example Devices Test/CN=Example Device Test CA" -out ca.pem
# 2. Three devices: each makes its own key; the CA signs a CSR whose CN is the thing name.
issue() { # $1 = device id, $2 = validity in days
openssl ecparam -name prime256v1 -genkey -noout -out "$1.key"
openssl req -new -key "$1.key" -subj "/CN=$1" -out "$1.csr"
openssl x509 -req -in "$1.csr" -CA ca.pem -CAkey ca.key -CAcreateserial \
-sha256 -days "$2" -out "$1.pem" 2>/dev/null
}
issue sensor-0001 730
issue sensor-0002 730
issue sensor-0003 20 # simulates a certificate near expiry
# 3. Verify each chain and report certificates that expire within 30 days.
report() { # $1 = certificate file
cn=$(openssl x509 -in "$1" -noout -subject | sed 's/.*CN *= *//')
chain=$(openssl verify -CAfile ca.pem "$1" | awk '{print $2}')
if openssl x509 -in "$1" -noout -checkend $((30*24*3600)) >/dev/null; then
exp="ok"
else
exp="EXPIRES <30d"
fi
echo "$1 CN=$cn chain=$chain expiry=$exp"
}
for c in sensor-000?.pem; do report "$c"; done
# 4. Rotation for sensor-0003: a new key and CSR "on the device", a new certificate.
# Keep the old pair until the device has connected with the new one.
mv sensor-0003.key old-0003.key && mv sensor-0003.pem old-0003.pem
issue sensor-0003 730
report old-0003.pem; report sensor-0003.pem
# 5. A root change seen from the device: the broker's new certificate comes from a
# new root; a device that trusts only the old root (ca.pem) rejects it.
openssl ecparam -name prime256v1 -genkey -noout -out root2.key
openssl req -x509 -new -key root2.key -sha256 -days 3650 \
-subj "/CN=Example New Root" -out root2.pem
openssl ecparam -name prime256v1 -genkey -noout -out broker.key
openssl req -new -key broker.key -subj "/CN=broker.example.com" -out broker.csr
openssl x509 -req -in broker.csr -CA root2.pem -CAkey root2.key -CAcreateserial \
-sha256 -days 365 -out broker.pem 2>/dev/null
openssl verify -CAfile ca.pem broker.pem || echo "=> rejected: old trust store"
cat ca.pem root2.pem > trust-updated.pem # after an over-the-air anchor update
openssl verify -CAfile trust-updated.pem broker.pem
# 6. Teardown: remove only the directory this script created.
cd / && rm -rf -- "$WORK" && echo "removed $WORK"
Expected output (the temporary directory name will differ):
work dir: /tmp/tmp.<random>
sensor-0001.pem CN=sensor-0001 chain=OK expiry=ok
sensor-0002.pem CN=sensor-0002 chain=OK expiry=ok
sensor-0003.pem CN=sensor-0003 chain=OK expiry=EXPIRES <30d
old-0003.pem CN=sensor-0003 chain=OK expiry=EXPIRES <30d
sensor-0003.pem CN=sensor-0003 chain=OK expiry=ok
CN = broker.example.com
error 20 at 0 depth lookup: unable to get local issuer certificate
error broker.pem: verification failed
=> rejected: old trust store
broker.pem: OK
removed /tmp/tmp.<random>
Validation: sensor-0003 is flagged, as AWS IoT Device Defender’s “Device certificate expiring” check would flag it (default
30 days, configurable up to 3,652).[60] In step 4 old and new certificates both verify, so the device can fall back
until the new one has connected. Step 5 is the device’s view of a root change: with only the old root it rejects the broker, as
un-updated clients did when DST Root CA X3 expired;[8] after an anchor update it accepts it. Troubleshooting: set -e
stops the script at a failing command; rerun with bash -x lab2.sh. Teardown: the script removes its own directory (after a
failure, delete the printed one); delete lab2.sh.
Lab 3 (advanced): a staged rollout that aborts
Link to section “Lab 3 (advanced): a staged rollout that aborts”Objective: see why the abort minimum must fit the canary, how a mixed wave dilutes a fault in one hardware class, and how a canary stratified by class catches it. Prerequisites: Python 3.8 or later; 20 minutes. Cost and risk: none (synthetic fleet with a fixed seed).
- Save the script as
rollout.pyand runpython3 rollout.py. - Run
python3 rollout.py --min-done 5and compare the first plan. - Run
python3 rollout.py --seed 3, thenpython3 rollout.py --seed 3 --canary-per-class 8. - Run
python3 rollout.py --seed 6 --canary-per-class 8and look at plan 2.
Example status: verified by execution (Python 3.8.10, 2026-10-18), seeds 1–7, with and without --canary-per-class 8. The rule
copies the shape of AWS IoT Jobs abort criteria but counts finished devices, where AWS counts notified ones; a per-class rule means
one job per hardware class.[64]
#!/usr/bin/env python3
"""Simulate a staged firmware rollout with eligibility and abort rules.
Synthetic fleet, deterministic seed, no network. The abort rule mirrors the
shape of AWS IoT Jobs abort criteria: abort when failures reach a threshold
percentage after a minimum number of devices have finished. With
--canary-per-class N the canary is the first N eligible devices of each
hardware class, and the rule is checked per class.
"""
import argparse
import random
WAVES = [0.01, 0.10, 0.50, 1.00] # cumulative share of eligible devices
def make_fleet(n, rng):
fleet = []
for i in range(n):
fleet.append({
"id": f"dev-{i:04d}",
"hw": "B" if rng.random() < 0.15 else "A", # 15% hardware rev B
"online": rng.random() > 0.03, # seen in last 24 h
"battery": int(rng.random() * 100),
})
return fleet
def outcome(dev, rng):
"""New firmware: fine on rev A, a driver fault on rev B (synthetic)."""
r = rng.random()
if dev["hw"] == "B":
return "FAILED" if r < 0.60 else "SUCCEEDED"
if r < 0.005:
return "FAILED"
return "TIMED_OUT" if r < 0.010 else "SUCCEEDED"
def run(fleet, classes, a):
rng = random.Random(a.seed)
eligible = [d for d in fleet if d["online"] and d["battery"] >= 30
and d["hw"] in classes]
offline = sum(not d["online"] for d in fleet)
low = sum(d["online"] and d["battery"] < 30 for d in fleet)
print(f"classes={sorted(classes)} eligible={len(eligible)} of {len(fleet)} "
f"(deferred: offline={offline}, battery<30%={low})")
canary = 0
if a.canary_per_class: # stratified canary goes first
picked = [d for hw in sorted(classes)
for d in [e for e in eligible if e["hw"] == hw][:a.canary_per_class]]
ids = {d["id"] for d in picked}
eligible = picked + [d for d in eligible if d["id"] not in ids]
canary = len(picked)
tally = {hw: [0, 0] for hw in sorted(classes)} # finished, failed
start = 0
for share in WAVES:
end = max(start + 1, round(len(eligible) * share))
if start == 0 and canary:
end = canary
for dev in eligible[start:end]:
failed = outcome(dev, rng) in ("FAILED", "TIMED_OUT")
tally[dev["hw"]][0] += 1
tally[dev["hw"]][1] += failed
groups = tally if canary else {
"all": [sum(t[0] for t in tally.values()),
sum(t[1] for t in tally.values())]}
wave = "canary" if start == 0 and canary else f"wave to {share:>4.0%}"
hits = []
for name, (done, bad) in groups.items():
rate = 100 * bad / done if done else 0.0
tag = f" rev {name}" if canary else ""
print(f" {wave}{tag}: finished={done:4d} failed={bad:3d} "
f"rate={rate:5.1f}%")
if done >= a.min_done and rate >= a.threshold:
hits.append(f"{tag} {rate:.1f}%")
if hits:
total = sum(t[0] for t in tally.values())
print(f" ABORT:{','.join(hits)} >= {a.threshold}% "
f"after {total} devices")
return
start = end
print(" COMPLETE")
def main():
p = argparse.ArgumentParser()
p.add_argument("--fleet", type=int, default=1000)
p.add_argument("--threshold", type=float, default=5.0, help="percent")
p.add_argument("--min-done", type=int, default=8)
p.add_argument("--seed", type=int, default=7)
p.add_argument("--canary-per-class", type=int, default=0,
help="0 = canary is the first 1%% of the list")
a = p.parse_args()
if a.canary_per_class < 0:
raise SystemExit("--canary-per-class must be 0 or more")
fleet = make_fleet(a.fleet, random.Random(a.seed))
print("Plan 1: manifest without a hardware class (targets A and B)")
run(fleet, {"A", "B"}, a)
print("Plan 2: class-aware manifest (rev A only; rev B gets its own build)")
run(fleet, {"A"}, a)
if __name__ == "__main__":
main()
Expected output (step 1, then the second command of step 3, plan 1 only):
Plan 1: manifest without a hardware class (targets A and B)
classes=['A', 'B'] eligible=682 of 1000 (deferred: offline=41, battery<30%=277)
wave to 1%: finished= 7 failed= 2 rate= 28.6%
wave to 10%: finished= 68 failed= 7 rate= 10.3%
ABORT: 10.3% >= 5.0% after 68 devices
Plan 2: class-aware manifest (rev A only; rev B gets its own build)
classes=['A'] eligible=573 of 1000 (deferred: offline=41, battery<30%=277)
wave to 1%: finished= 6 failed= 0 rate= 0.0%
wave to 10%: finished= 57 failed= 0 rate= 0.0%
wave to 50%: finished= 286 failed= 3 rate= 1.0%
wave to 100%: finished= 573 failed= 8 rate= 1.4%
COMPLETE
Plan 1: manifest without a hardware class (targets A and B)
classes=['A', 'B'] eligible=689 of 1000 (deferred: offline=21, battery<30%=290)
canary rev A: finished= 8 failed= 0 rate= 0.0%
canary rev B: finished= 8 failed= 5 rate= 62.5%
ABORT: rev B 62.5% >= 5.0% after 16 devices
Validation: in plan 1 the canary fails 2 of 7 devices but cannot abort below the minimum of 8; with --min-done 5 it aborts
there. With seed 3 the minimum hides the canary’s failure again, and in the 10% wave rev A successes dilute rev B’s failures to
4.3%, so the job aborts after 344 devices. A canary of 8 per class, checked per class, stops it after 16. Seed 6 shows the price:
one ordinary rev A failure in 8 devices is 12.5%, so small strata with a 5% threshold also stop good builds; size them against the
normal failure rate. Plan 2 completes; its 318 deferred devices remain open work. Troubleshooting: other numbers on another
Python version mean a changed random generator; --canary-per-class must be 0 or more means a negative value. Teardown:
delete rollout.py.
Use cases and scenarios
Link to section “Use cases and scenarios”1. Battery soil sensors on LoRaWAN (illustrative). 3,000 Class A sensors on a farm cooperative. Method: binary payloads, configuration queued until the next uplink, firmware updates only after switching a group to Class B or C.[50] Success measure: battery forecast per firmware version within 10% of the field trend. Pitfall: treating downlinks as free; a transmitting gateway cannot receive.[69]
2. A consumer smart lock sold in the UK and the EU (illustrative, sourced lessons). Method: unique credentials per device (ETSI 5.1-1, UK PSTI),[29][32] a support period of at least five years (CRA, from December 2027),[70] class-checked signed updates with revert, and a CRA reporting runbook. Lesson: LockState’s wrong-model update (see The problem it solves). Success measure: zero wrong-class installs; a reporting drill within 24 hours. Pitfall: treating the support period as marketing copy.
3. Cameras and access controllers across 200 sites (illustrative). The gap SecuriThings described: device maintenance falling “in a gap between IT and operational teams”.[10] Method: one inventory across sites, firmware currency and password rotation tracked per device. Success measure: share of devices on supported firmware with rotated credentials. Pitfall: unclear ownership; here, the embedded Linux cameras stay on this page and site gateways go to EdgeOps.
4. Cellular alarms facing a network shutdown (sourced). Before AT&T’s 3G shutdown, scheduled for 22 February 2022, an alarm-industry group estimated that 1.5 million alarm customers still needed upgrades and about half a million medical-alert devices ran on 3G; an unnetworked fire alarm still sounds but cannot call the fire service.[9] Method: record radio and network per device, track operator sunset notices quarterly, budget replacements years ahead. Success measure: no device on a network whose announced shutdown falls inside its support period.
Measure, govern, pay
Link to section “Measure, govern, pay”Metrics and KPIs
Link to section “Metrics and KPIs”| KPI | Kind | Formula | Data source | Pitfall |
|---|---|---|---|---|
| Fleet reachability | Leading | Devices heard from within expected interval plus grace ÷ devices expected to report | Fleet index | Dividing by “connected now” hides the silent |
| Firmware currency | Lagging | Devices on a supported firmware ÷ active devices | Fleet index | Counting deferred devices as done |
| Wave failure rate | Leading | (Failed + timed out + rejected) ÷ devices that finished the job | Job service | Ignoring devices still queued |
| Unrecoverable rate | Lagging | Devices lost to management after an update ÷ devices updated | Fleet index, field tickets | Revert hides the fault; count reverts too |
| Time to 90% patched | Lagging | Release approval to 90% of reachable targets on the fixed version | Job service | Silently excluding unreachable devices |
| Identity hygiene | Leading | Devices with unique, unexpired credentials ÷ active devices | CA, audit[48] | Expiry alerts after the fact |
| Battery risk | Leading | Devices forecast empty within 90 days ÷ battery devices | Battery telemetry | Ignoring firmware version |
| Cost per device-month | Lagging | Platform + connectivity + field service ÷ active devices | Bills, tickets | Leaving out site visits |
Leading KPIs (this page’s classification) trigger a halt, a visit or a rotation job; lagging ones confirm outcomes. Set targets per hardware class from your own baseline; revisit them quarterly.
Worked calculation (illustrative fleet). 10,000 devices should report every 4 hours, with 2 hours’ grace; 9,412 were heard from in the last 6 hours: reachability 94.1%. A security release reached 8,830 devices: 8,700 confirmed, 55 reverted and reported failure, 75 timed out. Wave failure rate = (55 + 75) ÷ 8,830 = 1.5%. Only 2.4.1 now counts as supported, so firmware currency = 8,700 ÷ 10,000 = 87.0%. Report the 1,300 devices not current, split into deferred (low battery), silent and failed, each with an owner.
Security, privacy and compliance
Link to section “Security, privacy and compliance”- Credentials. No universal defaults (ETSI provision 5.1-1; UK PSTI);[29][32] one certificate per device, which AWS’s “Device certificate shared” audit rates critical when it finds sharing.[59]
- Least privilege per device on topics and state; MUD lets a device declare the network access it needs.[49] Close debug ports in production builds.[28]
- Update integrity. Signed images, class checks and monotonic sequence numbers against downgrade.[36] Going back is still possible. An unconfirmed image that reverts returns to the confirmed version, which is not a downgrade; an authorised rollback is a new signed decision, because the sequence number belongs to the manifest: “A firmware version may be rolled back by creating a new manifest for the old firmware version with a later sequence number”.[36] NIST lists rollback by authorised entities, noting that some organisations prefer to rule it out;[28] MCUboot’s optional rollback protection uses a security counter separate from the version.[35]
- Vulnerability handling. Since 11 September 2026, manufacturers of products on the EU market, including products already sold, must report actively exploited vulnerabilities and severe incidents affecting product security through ENISA’s single reporting platform: early warning within 24 hours, notification within 72 hours, final report 14 days after a fix is available (vulnerabilities) or a month after the notification (incidents).[78][37] From 11 December 2027 Annex I adds, where applicable, automatic security updates on by default with an opt-out, and a machine-readable software bill of materials (SBOM) of at least top-level dependencies;[70] see DevSecOps and VulnerabilityOps.
- Privacy. Telemetry from homes and people can be personal data; legal basis belongs to PrivacyOps, data removal to the decommissioning playbook.
Cost, capacity and sustainability
Link to section “Cost, capacity and sustainability”Platform cost formula (AWS, Europe (Ireland) example prices, as of October 2026). Messages cost $1 per million for the first billion and are “metered in 5 KB increments”; always-on connectivity costs $0.08 per million connection minutes, so 10,000 devices connected for 30 days use 432,000,000 minutes, or $34.56.[77] Shadow operations are metered in 1 KB steps,[77] so frequent, large state writes multiply metered operations, and each document is capped at 8 KB.[65]
Device-to-cloud messaging cost = reports per device per month × devices × ⌈payload ÷ 5 KB⌉ × price per message (Lab 1 computes it). AWS also bills messages delivered to subscribers and, separately, rules engine use; messages sent through the Basic Ingest reserved topic carry no messaging charge.[77] Connectivity (SIM data plans or LPWAN fees, from the operator) is its own line. One site visit at an assumed $150 costs more than a month of messaging for Lab 1’s fleet ($86.40), so reachability and the unrecoverable rate are cost metrics too.
Capacity. AWS IoT Core discards publish requests above 100 per second per connection and allows one CONNECT per second per client ID;[65] duplicated client IDs and reconnect storms hit these first. Sustainability: battery replacements and devices bricked or abandoned early are waste; carbon accounting is in GreenOps.
Assurance of the operating model
Link to section “Assurance of the operating model”| Risk indicator | Healthy signal | Check |
|---|---|---|
| Silent devices without an owner | None older than a week | Weekly fleet review |
| Certificates expiring within 90 days without a rotation job | None | Monthly audit |
| Rollouts without a written abort rule | None | Change record |
| Devices on networks or platforms with an announced end date | Each has a migration date | Quarterly lifecycle review |
| CRA reporting path (exploited vulnerability, severe incident) untested | One drill per year | Drill record |
When things go wrong
Link to section “When things go wrong”Common failures and troubleshooting
Link to section “Common failures and troubleshooting”| Symptom | Likely cause | Diagnose | Fix and prevent |
|---|---|---|---|
| Devices connect and drop in a loop | Two devices share a client ID; each connect takes over the other’s session[23] | Audit “Conflicting MQTT client IDs”[48] | Client ID = thing name = certificate CN |
| Broker overloaded after an outage | Synchronised reconnects | Connect rate by second | Jittered exponential backoff[66] |
| Commands never reach sleeping devices | Session expired; Class A timing | Session expiry, LoRaWAN class | Use desired state[24][50] |
| State costs rising, updates rejected | Telemetry written to the shadow | Shadow size and operations | Telemetry as messages[25][65] |
| TLS failures across one model | Trust store lacks a new root | Model and firmware of failing devices | Update anchors before root changes[8] |
| Bill doubles after a release | Payload crossed a 5 KB step | Payload size by version | Binary encoding, batching[77] |
| Battery drains faster after a release | Radio or interval changed | Battery slope by firmware version | Budget review before release |
| Rollout stuck at a low rate | Rate increase waits for successes | Job execution counts | Check the criteria[64] |
Worked runbook: a firmware wave starts losing devices
Link to section “Worked runbook: a firmware wave starts losing devices”Diagram source (Mermaid)
flowchart TB
A["Failures rise in a wave"]:::risk --> B["Halt rollout: cancel queued only"]:::core
B --> C{"One class, region or link?"}:::decision
C -->|yes| D["Exclude that stratum"]:::core
C -->|no| E["Treat build as bad"]:::risk
D --> F{"Devices reverted?"}:::decision
E --> F
F -->|yes| G["Fix build, new canary"]:::core
F -->|"no, still silent"| H{"Reachable by another path?"}:::decision
H -->|yes| I["Recovery job or reset"]:::core
H -->|no| J["Field service, notify customers"]:::risk
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
Text equivalent: when failures rise in a wave, halt the rollout by cancelling queued executions only. If the failures concentrate in one hardware class, region or link type, exclude that stratum; otherwise treat the build as bad. If affected devices reverted, fix the build and start a new canary. If they are still silent, use another path (a recovery job or a remote reset) where one exists; otherwise plan field service and notify customers.
Example status: commands passed AWS CLI 1.18.69 parameter validation with credentials disabled; not executed against AWS.
- Stop new installs.
aws iot cancel-job --job-id fw-2-4-1-revA-wave2 --reason-code DEVICE_LOSS --comment "halted". Without--forceonly queued executions are cancelled; with it, in-progress ones too, and AWS warns to “ensure that each device executing a job which is canceled is able to recover to a valid state”.[79] Do not force-cancel mid-write devices. - Size the damage.
aws iot list-job-executions-for-job --job-id fw-2-4-1-revA-wave2 --status FAILED, thenTIMED_OUT; join with the fleet index on hardware revision, radio, region and battery. - Classify. Reverted and reporting (recoverable); silent but within the expected interval (wait); silent past it (lost until proven otherwise).
- Communicate. Field service and support get the affected list; if the release fixed an exploited flaw, the CRA clock (Security, privacy and compliance) still runs.
- Recover. A fixed build goes to a new, stratified canary; lost devices need a visit or a postal swap.[5]
- Prevent. Add the missing check to eligibility, the class check or the self-test; record the review in IncidentOps.
Anti-patterns and myths
Link to section “Anti-patterns and myths”- “We will add OTA after launch.” Without a bootloader, slots and keys in the first build, there is nothing to add it to.
- One credential per product line. One extracted key compromises every unit.
- The shadow as a database. It holds desired and last-reported state; history belongs in messages.[25]
- Myth: QoS 2 makes delivery reliable end to end. MQTT QoS is between client and broker;[23] sleeping devices and expired sessions still lose commands.
Deciding and adopting
Link to section “Deciding and adopting”When to use IoTOps and when not to
Link to section “When to use IoTOps and when not to”Use it when devices outnumber what people can manage by hand,[1] stay in the field for years, sit where visits are expensive, or fall under consumer-product law. Do not build it for a dozen lab prototypes, for user devices (EndpointOps) or site servers (EdgeOps), or when a supplier operates the fleet as a service: govern the contract (support period, update service levels, data return). Class 0 devices “generally cannot be secured or managed comprehensively”;[21] manage their gateway and plan to replace them.
Decision framework
Link to section “Decision framework”Diagram source (Mermaid)
flowchart TB
Z{"Supplier-run, or a few lab units?"}:::decision -->|yes| Y["Do not build: govern the contract"]:::risk
Z -->|no| A{"Devices updated remotely?"}:::decision
A -->|no| B["Fixed-function: plan replacement"]:::risk
A -->|yes| C{"Microcontroller class?"}:::decision
C -->|yes| D["Microcontroller bootloader: test, confirm"]:::core
C -->|"no, Linux"| E["A/B image updater"]:::core
D --> F{"Team can run brokers 24x7?"}:::decision
E --> F
F -->|no| G["Managed platform plus exit plan"]:::core
F -->|yes| H{"Data residency or lock-in critical?"}:::decision
H -->|yes| I["Self-hosted stack"]:::core
H -->|no| G
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
Text equivalent: if a supplier operates the fleet as a service, or there are only a few lab units, do not build an IoTOps practice: govern the contract or keep the prototypes manual. Otherwise, if devices cannot be updated remotely, they are fixed-function and need a replacement plan. If they can, choose a microcontroller bootloader with test-and-confirm boot, or an A/B image updater for Linux devices. Then, if the team cannot run brokers around the clock, use a managed platform with an exit plan; if it can, choose a self-hosted stack when data residency or lock-in is critical, and a managed platform otherwise.
Maturity model
Link to section “Maturity model”This Atlas’s model, not an industry standard.
| Level | Observable behaviour | Evidence | Next step |
|---|---|---|---|
| 0. Hand-flashed | Shared credentials; updates need a cable | None | Per-device identity |
| 1. Inventoried | Registry with unique identities; last-seen per device | Registry export | Signed OTA with revert |
| 2. Updatable | Signed, class-checked updates with test and confirm | Successful revert drill | Staged rollouts with abort rules |
| 3. Governed | Stratified canaries, abort rules, fleet KPIs, rotation jobs | KPI trend, job records | Lifecycle planning |
| 4. Lifecycle-managed | Published support periods, sunset tracking, reporting drills, exit plan | Lifecycle review minutes | Keep pace with regulation |
Adoption roadmap: 30/60/90 days
Link to section “Adoption roadmap: 30/60/90 days”| Window | Actions | Exit criteria |
|---|---|---|
| Days 1–30 | Inventory devices, credentials, firmware and networks; find shared credentials; run Lab 1 on real currents | Gaps listed with owners |
| Days 31–60 | Per-device identity for new units; least-privilege policy; abort rules; check-in KPI | One release through a stratified canary |
| Days 61–90 | Revert drill; certificate-expiry audit and one rotation; support-period statement; reporting runbook | Drills passed; statement published |
Toolkit
Link to section “Toolkit”Checklists
Link to section “Checklists”Before a device model ships
- Bootloader verifies signatures; two slots or a recovery image; revert drill passed
- Key generated on the device or in a secure element; rotation rehearsed
- Hardware class and sequence number checked before install
- Self-test reconnects to the service before confirming
- Energy and message budget reviewed with measured currents
- Spare trust anchor; trust store updatable over the air
- Support period, vulnerability contact and end-of-life plan published
Before every rollout
- Canary covers every hardware revision, radio and region in scope
- Abort minimum no larger than the canary; timeout set
- Deferred devices (offline, low battery) counted, with a follow-up date
- Halt owner on call; cancel procedure known (queued or in progress)
Decommissioning
- Certificate revoked (self-hosted: CRL updated); registry, groups and allow-lists cleaned
- Data and settings wiped; ownership transferred or device disposed of
Templates
Link to section “Templates”DEVICE LIFECYCLE STATEMENT (one per model, reviewed yearly)
Model / hardware classes: <model> / <class IDs>
Support period ends: <YYYY-MM> Basis: <law or contract, e.g. CRA, PSTI>
Security contact: <URL or address> Acknowledge within: <n days>
Update path: <bootloader, slots, signing key owner>
Connectivity and end dates: <radio, operator, announced sunsets>
Platform and exit plan: <service, notice period, export of identities>
End of life: <final update, data wipe, authorisation handover>
FIRMWARE ROLLOUT RECORD
Release / sequence / class: <version> / <sequence> / <class ID>
Canary strata: <revisions, radios, regions, count per stratum>
Abort rule: <failure types> >= <n>% after <m> devices
Timeout / maintenance window: <minutes> / <window>
Halt owner and second: <name> / <name>
Result per wave: <succeeded / failed / timed out / deferred>
Decision: continue | halt | rebuild Signed: <name, date>
Deep comparisons
Link to section “Deep comparisons”| Protocol or model | Best for | Interaction | Watch out for |
|---|---|---|---|
| MQTT 3.1.1 / 5.0[23] | Always-on or regularly connected devices | Publish/subscribe through a broker | Keep-alive traffic; session expiry |
| CoAP[22] | Class 1 devices on lossy networks | Request/response over UDP | Confirmable or not; proxies |
| LwM2M[42][80] | Standard device-management objects | CoAP over UDP, TCP, SMS or non-IP links (3GPP CIoT, LoRaWAN); 1.2 adds HTTP and MQTT | Object-model support in your platform |
| LoRaWAN Class A[50] | Years on a battery, small payloads | Uplink first; downlink after | Duty cycle; firmware updates need Class B or C |
Is IoTOps a discipline, a job title or a product? None formally (see What IoTOps is not); search for IoT device management and firmware update.
Shadow, twin or digital twin? AWS’s shadow and Azure’s twin store a device’s desired and reported state;[24][25] “digital twin” frameworks such as Eclipse Ditto model things more broadly.[43]
How long should device certificates live? Long enough that a device on a shelf does not wake up expired, short enough that rotation stays routine; alert early[60] and rehearse the rotation playbook on the bench.
Do microcontrollers really need two slots? They need a recovery strategy: RFC 9019 allows a second image or a recovery image that can fetch a new one.[2] Without either, a bad image is a field visit.
Does a managed platform remove IoTOps? It removes broker operations, not identity, update, budget or lifecycle decisions, and it adds a dependency with its own end date.[6][72]
Going further
Link to section “Going further”Advanced topics and emerging direction
Link to section “Advanced topics and emerging direction”- Standard manifests. The IETF SUIT manifest format is approved and in the RFC Editor queue (as of October 2026).[81]
- Attestation. The remote attestation (RATS) architecture defines how a verifier judges evidence that a device is in an intended state;[82] an IOTOPS working-group draft, “MUD-Based RATS Resources Discovery”, links MUD with attestation (as of October 2026).[1]
- Regulated operations. With CRA reporting in force and the main obligations due in December 2027, this Atlas expects reporting drills and support-period records to become routine audit evidence.[33]
Learning path
Link to section “Learning path”| Stage | Do this | Proof |
|---|---|---|
| 1 day | Read RFC 7228 sections 2–3 and the IETF IOTOPS charter; run Lab 1 | Explain Class 0 versus Class 2 and one budget trade-off |
| 1 week | Labs 2 and 3; a free-tier device platform with one simulated device | Per-device policy and a working shadow update |
| 30 days | A development board with MCUboot or an A/B Linux image; a revert drill | Video or log of a forced revert |
| Advanced | Write a lifecycle statement and rollout policy for a fictional product | Reviewed by security and product |
By role. Beginners: the glossary, Labs 1 and 2. Practitioners: the reference implementation, Lab 3, the playbooks and the runbook. Leaders and architects: the lifecycle statement in Templates, the decision framework and the judgement call.
Wrap-up
Link to section “Wrap-up”Executive recap and cheat sheet
Link to section “Executive recap and cheat sheet”- Decide: support period, identity scheme, update mechanism, platform and exit plan, budget per device.
- Do: per-device credentials and rotation; signed, class-checked updates with test and confirm; stratified canaries with abort rules; desired state for sleeping devices; jittered reconnects.
- Measure: reachability, firmware currency, wave failure and unrecoverable rates, identity hygiene, battery risk, cost per device.
- Watch out for: shared claim keys, root changes, metering steps, network and platform retirements, forced cancellation.
A judgement call: an exploited flaw, a canary or everyone at once?
Link to section “A judgement call: an exploited flaw, a canary or everyone at once?”Situation (illustrative). An actively exploited flaw affects 40,000 locks in the EU. A fixed build passed the bench. The product lead wants it everywhere today; the platform lead wants the usual canary.
| Option | For | Against |
|---|---|---|
| A. Push to all devices now | Shortest exposure | A build fault hits every device; a lock that cannot reconnect needs a visit |
| B. Compressed canary: about 1% per hardware revision, then 10%, then all, within 24 hours, abort rules on | Contains a bad build | Exposure lasts hours longer |
Assumptions: devices revert unconfirmed images; a configuration change (disabling the exposed service) can go out through desired state first; the early warning is due within 24 hours either way.[78]
Decision: B, after a configuration mitigation. Illustrative arithmetic: if the build strands 2% of devices, a 400-device canary costs 8 site visits and option A costs 800. Option A looks responsible, but bricked devices lose the path for the next fix too. Uncertainty: if exploitation causes physical harm and the bench covered every hardware revision, a shorter canary may be justified; record the reasoning either way.
Learning objectives and mastery check
Link to section “Learning objectives and mastery check”After this page you can explain what IoTOps covers and why the label is niche; design an identity, rotation and update scheme; measure fleet health honestly; decide platform, rollout and lifecycle trade-offs.
- Your dashboard shows 99% of connected devices are healthy. Why may that mislead?
Show answer for question 1
Connected devices exclude the silent ones. Divide by devices expected to report (see Metrics and KPIs).
- A firmware wave’s abort rule needs 20 devices, but the canary has 12. What happens if all 12 fail?
Show answer for question 2
An AWS IoT job cannot abort until 20 devices have been notified.[64] If the canary is its own job, as in the reference implementation, it ends with 12 failures and no abort, and only a human gate stops wave 2; if one job targets the whole fleet, the rollout continues into the next devices (Lab 3).
- Why should the self-test before confirming an image include reconnecting to the service?
Show answer for question 3
A device that boots but cannot reconnect would confirm a build that strands it; unconfirmed images revert at the next reset.[35]
- A device reports every 5 minutes with a 6 KB JSON payload. Name two changes that cut its message cost.
Show answer for question 4
Shrink the payload below 5 KB (one metered message instead of two) and batch readings.[77]
- How do you change a setting on a LoRaWAN Class A sensor that sleeps for 30 minutes?
Show answer for question 5
Queue it as desired state; the network can deliver it only in the receive windows after the sensor’s next uplink.[50]
- Your platform provider announces a retirement in three years. What do you check first?
Show answer for question 6
Conclusion
Link to section “Conclusion”“IoTOps” is a working-group name and a short-lived marketing word, not a discipline. The work behind it is real and unforgiving: a device in a field, a wall or a lamp post must take every change without a human nearby, on a battery and a link someone else controls, for longer than many platforms and networks last. Teams that do it well decide the end before the beginning: one identity per device, an update path that can revert, rollouts that stop themselves, budgets set before release, and a support period they mean to keep.
Sources and further reading
Link to section “Sources and further reading”Evidence per claim: research/iotops-ledger.md. Prices, quotas, platform behaviour and retirement dates change; re-check vendor
pages before relying on them. All sources checked 2026-10-18.
Changelog. 2026-10-18, r0: first version (research, ledger, six diagrams, Labs 1–3 executed, AWS job parameters validated
offline).
2026-10-18, r1: revised after independent review (reviews/iotops-r1-response.md): CRA and UK dates, Mosquitto CRL, rotation
playbook, Labs 2–3 extended and re-run, cost, handshake, rollback, diagrams, EdgeOps refinement; five sources added.
2026-10-18, post-score polish (reviews/iotops-polish.md, reviews/iotops-polish-check.md): LwM2M bindings, PSTI passwords,
Figure 3 edges, a fallback-safe confirm script, KPI kinds, repetition trimmed; three footnotes added.
IOT Operations (iotops), About and Documents — IETF Datatracker, charter-ietf-iotops-02. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
RFC 9019, A Firmware Update Architecture for Internet of Things — IETF, April 2021. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Understanding the Mirai Botnet — Antonakakis et al., USENIX Security Symposium, 2017. Checked 2026-10-18. ↩︎
Botched Firmware Update Bricks Hundreds of Smart Door Locks — BleepingComputer, 2017-08-12. Checked 2026-10-18. ↩︎
Lockstate bricks smart locks with dumb firmware upgrade — The Register, 2017-08-12. Checked 2026-10-18. ↩︎ ↩︎
Google Cloud to retire IoT Core service in 2023 — Praharsha Anand, IT Pro, 2022-08-18 (quotes Google’s customer email). Checked 2026-10-18. ↩︎ ↩︎ ↩︎
Cloud IoT Core documentation, archived 2023-06-05 — Google Cloud, banner “Google Cloud IoT Core is being retired on August 16, 2023” (Internet Archive copy). Checked 2026-10-18. ↩︎
DST Root CA X3 Expiration (September 2021) — Let’s Encrypt, updated 2024-02-05. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
EXPLAINER: What 3G shutdown means for burglar alarms — Tali Arbel, Associated Press (via WBRC), 2022-02-15. Checked 2026-10-18. ↩︎ ↩︎
The Ultimate Guide to IoTOps — SecuriThings, undated. Checked 2026-10-18. ↩︎ ↩︎
SecuriThings raises $14M to expand functionality of its IoTOps platform — Help Net Security, 2021-02-12. Checked 2026-10-18. ↩︎
SecuriThings home page — SecuriThings, page as of 2026-10-18. Checked 2026-10-18. ↩︎
What is Azure IoT Operations? — Microsoft Learn, updated 2025-09-24. Checked 2026-10-18. ↩︎
az iot ops — Microsoft Learn, Azure CLI reference. Checked 2026-10-18. ↩︎
Tech guidance, Cisco IoT Operations Dashboard — Cisco DevNet, undated. Checked 2026-10-18. ↩︎
IoT 24x7 Operations (IoT-OPS) — JIG-SAW US, undated. Checked 2026-10-18. ↩︎
8 Reasons Why IoT Operations (IoTOps) Is the Future of Developer Productivity — Leah Zitter, C2C Global, 2021-03-03. Checked 2026-10-18. ↩︎
draft-ietf-iotops-7228bis, Terminology for Constrained-Node Networks — IETF, revision of 2026-07-06, in the RFC Editor queue. Checked 2026-10-18. ↩︎
SP 800-82 Rev. 3, Guide to Operational Technology (OT) Security — NIST, September 2023 (links the Rev. 4 draft). Checked 2026-10-18. ↩︎ ↩︎
ISA/IEC 62443 Series of Standards — ISA. Checked 2026-10-18. ↩︎ ↩︎
RFC 7228, Terminology for Constrained-Node Networks — IETF, May 2014. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
RFC 7252, The Constrained Application Protocol (CoAP) — IETF, June 2014. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
MQTT Version 5.0 — OASIS Standard, 2019-03-07 (lists MQTT 3.1.1, OASIS Standard of 2014-10-29, as related work). Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
AWS IoT Device Shadow service — AWS documentation. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Understand and use device twins in IoT Hub — Microsoft Learn, updated 2024-12-17. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
What is Azure IoT Hub Device Provisioning Service? — Microsoft Learn, updated 2025-02-27. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
How job configurations work — AWS IoT Core documentation. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
NISTIR 8259A, IoT Device Cybersecurity Capability Core Baseline — NIST, May 2020. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
ETSI EN 303 645 V3.1.3, Cyber Security for Consumer Internet of Things: Baseline Requirements — ETSI, September 2024. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
IR 8259 Rev. 1, Foundational Cybersecurity Activities for IoT Product Manufacturers — NIST, April 2026. Checked 2026-10-18. ↩︎ ↩︎
NIST Cybersecurity for IoT Program — NIST. Checked 2026-10-18. ↩︎
The UK Product Security and Telecommunications Infrastructure (Product Security) regime — DSIT, GOV.UK. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
Cyber Resilience Act — European Commission. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
Device Shadow service documents — AWS documentation. Checked 2026-10-18. ↩︎ ↩︎
MCUboot design — MCUboot project documentation. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
RFC 9124, A Manifest Information Model for Firmware Updates in IoT Devices — IETF, January 2022. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Cyber Resilience Act, summary of the legislative text — European Commission. Checked 2026-10-18. ↩︎ ↩︎
IoT Lens, AWS Well-Architected Framework: design principles — AWS, published 2025-07-02. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
RFC 8995, Bootstrapping Remote Secure Key Infrastructure (BRSKI) — IETF, May 2021. Checked 2026-10-18. ↩︎
FIDO Device Onboard (FDO) Overview — FIDO Alliance. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
Provisioning devices that don’t have device certificates using fleet provisioning — AWS documentation. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
What is Eclipse Ditto? — Eclipse Foundation. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
Mender: introduction — Northern.tech, Mender documentation. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
RAUC Basics and Using RAUC — RAUC documentation. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
Eclipse hawkBit — Eclipse Foundation, GitHub README. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
What is Device Update for IoT Hub? — Microsoft Learn, updated 2026-10-05. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
Audit checks — AWS IoT Device Defender documentation. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
RFC 8520, Manufacturer Usage Description Specification — IETF, March 2019. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
LoRaWAN device classes — The Things Network documentation. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Duty cycle — The Things Network documentation. Checked 2026-10-18. ↩︎
Power saving techniques (cellular) — Nordic Semiconductor, nRF Connect SDK documentation. Checked 2026-10-18 (read with WebFetch). ↩︎
RFC 8576, Internet of Things (IoT) Security: State of the Art and Challenges — IRTF, April 2019. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
Create and manage jobs: presigned URLs — AWS IoT Core documentation. Checked 2026-10-18. ↩︎ ↩︎
Lifecycle events — AWS IoT Core documentation. Checked 2026-10-18. ↩︎
mosquitto.conf manual page and mosquitto(8) (signals) — Eclipse Mosquitto. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
ThingsBoard — ThingsBoard, GitHub. Checked 2026-10-18. ↩︎ ↩︎
aws iot create-job — AWS CLI command reference. Checked 2026-10-18. ↩︎ ↩︎
Device certificate shared — AWS IoT Device Defender documentation. Checked 2026-10-18. ↩︎ ↩︎
Device certificate expiring — AWS IoT Device Defender documentation. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
How to manage IoT device certificate rotation using AWS IoT — Ryan Dsouza and lukmal, The Internet of Things on AWS blog, 2022-03-28. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
CreateCertificateFromCsr — AWS IoT API reference. Checked 2026-10-18. ↩︎
Thing policy variables — AWS documentation. Checked 2026-10-18. ↩︎ ↩︎
AbortCriteria and ExponentialRolloutRate — AWS IoT API reference. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
AWS IoT Core endpoints and quotas — AWS General Reference. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Exponential Backoff And Jitter — Marc Brooker, AWS Architecture Blog, 2015-03-04. Checked 2026-10-18. ↩︎ ↩︎
RFC 7925, TLS / DTLS Profiles for the Internet of Things — IETF, July 2016. Checked 2026-10-18. ↩︎
RFC 9146, Connection Identifier for DTLS 1.2 — IETF, March 2022. Checked 2026-10-18. ↩︎
LoRaWAN limitations — The Things Network documentation. Checked 2026-10-18. ↩︎ ↩︎
Regulation (EU) 2024/2847 (Cyber Resilience Act), Article 13 and Annex I — EUR-Lex, 2024; third-party copies: Annex I, Article 13 (StreamLex). Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
SP 800-213, IoT Device Cybersecurity Guidance for the Federal Government — NIST, November 2021. Checked 2026-10-18. ↩︎
AZ569914, Retirement: Azure IoT Central will be retired on September 20, 2029 — Microsoft Azure Updates, 2026-09-29 (text read in the third-party Message Center Archive). Checked 2026-10-18. ↩︎ ↩︎ ↩︎
Microsoft Confirms Azure IoT Central Commitment — Automation World, 2024. Checked 2026-10-18. ↩︎
Publish/Subscribe policy examples — AWS documentation. Checked 2026-10-18. ↩︎ ↩︎
os-release — systemd 262 manual, freedesktop.org. Checked 2026-10-18. ↩︎
MCUboot image control API — Zephyr API documentation (4.5.0-rc2). Checked 2026-10-18. ↩︎
AWS IoT Core pricing — Amazon Web Services, prices as of October 2026. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
CRA reporting obligations — European Commission. Checked 2026-10-18. ↩︎ ↩︎
aws iot cancel-job and list-job-executions-for-job — AWS CLI command reference. Checked 2026-10-18. ↩︎
Lightweight M2M Transport Bindings, version 1.2 — OMA, 2020-11-10. Checked 2026-10-18. ↩︎
draft-ietf-suit-manifest — IETF Datatracker, IESG state “RFC Ed Queue”. Checked 2026-10-18. ↩︎
RFC 9334, Remote ATtestation procedureS (RATS) Architecture — IETF, January 2023. Checked 2026-10-18. ↩︎