Fleet Device Provisioning
Provisioning 10 devices is a spreadsheet and a prayer. Provisioning 10,000 is a factory line, a provisioning service, and a failure handling strategy for the 2% that don't connect on first boot. I've seen fleets where every device shipped with the same MQTT password — fine for a demo, catastrophic at scale. Fleet provisioning is the infrastructure that gives each device a unique identity before it leaves the factory.
Provisioning stages
Factory floor First boot Operational
───────────── ────────── ────────────
Generate key pair → Connect with claim → Unique cert + config
Flash firmware cert to provisioning Full cloud access
Print serial label service Claim cert revoked
QA test station Receive operational
certificate + policy
Three distinct phases, three distinct credential sets.
Factory programming
The factory station runs automated tests:
def factory_provision(device_serial: str, device_port: str) -> ProvisioningResult:
device = connect_uart(device_port)
# Verify firmware version
fw_version = device.get_firmware_version()
assert fw_version >= MIN_FIRMWARE, f"Outdated firmware: {fw_version}"
# Verify claim certificate is present
claim_fp = device.get_certificate_fingerprint("claim")
assert claim_fp in KNOWN_CLAIM_CERTS, "Unknown claim certificate"
# Trigger provisioning
result = device.run_provisioning(
endpoint=PROVISIONING_ENDPOINT,
template="production-v2",
)
assert result.success, f"Provisioning failed: {result.error}"
# Verify operational certificate
op_fp = device.get_certificate_fingerprint("operational")
registry.register(device_serial, op_fp, claim_fp)
# Functional test
device.publish_test_message()
assert device.wait_for_command(timeout=30), "Command delivery failed"
return ProvisioningResult(serial=device_serial, cert_fingerprint=op_fp)
Every device gets a record: serial, certificate fingerprint, provisioning timestamp, test results.
Claim certificate design
The claim certificate is deliberately limited:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["iot:Connect", "iot:Publish", "iot:Receive"],
"Resource": "*",
"Condition": {
"StringEquals": { "iot:Connection.Thing.ProvisioningTemplate": "production-v2" }
}
}]
}
It can only connect to the provisioning endpoint and call RegisterThing. Nothing else.
All devices in a batch share the same claim certificate — that's acceptable because:
- It's revoked after first use (per device)
- It can only register, not operate
- Compromise of one claim cert only affects unprovisioned devices
Bulk registration API
For devices provisioned offline (no cloud during factory):
@app.post("/api/v1/devices/bulk-register")
def bulk_register(devices: list[BulkDevice]):
results = []
for device in devices:
try:
cert = ca.sign_csr(device.csr, subject=f"CN={device.serial}")
thing = iot.create_thing(thing_name=device.serial, thing_type=device.type)
iot.attach_certificate(thing.thing_name, cert.certificate_arn)
iot.attach_policy(f"policy-{device.type}", cert.certificate_arn)
results.append({"serial": device.serial, "status": "registered"})
except Exception as e:
results.append({"serial": device.serial, "status": "failed", "error": str(e)})
return results
The factory uploads a CSV of serial numbers and CSRs; the service returns signed certificates for flashing.
First-boot provisioning flow
On first power-on in the field:
def first_boot_provision(config):
if has_operational_cert():
return connect_operational()
claim_cert, claim_key = load_claim_credentials()
client = mqtt_connect(config.provisioning_endpoint, claim_cert, claim_key)
register_request = {
"certificateSigningRequest": generate_csr(),
"parameters": {
"SerialNumber": get_device_serial(),
"DeviceType": config.device_type,
}
}
response = client.publish_and_wait(
"$aws/provisioning-templates/{}/provision/json".format(config.template),
register_request,
timeout=60,
)
save_operational_cert(response["certificatePem"], response["privateKey"])
revoke_claim_cert(claim_cert)
connect_operational()
Retry with exponential backoff if the provisioning endpoint is unreachable. Store a "provisioning attempted" flag to distinguish first boot from reboot.
Handling failures
| Failure | Detection | Recovery |
|---|---|---|
| Provisioning timeout | No cert after 5 min | Retry 3x, then enter safe mode with LED blink pattern |
| Duplicate serial | Provisioning service rejects | Quarantine, alert factory QA |
| Claim cert revoked | TLS handshake fails | RMA — return to factory for reprogramming |
| Partial provisioning | Operational cert saved but policy not attached | Idempotent re-provision with same CSR |
| Network unavailable | DNS/connect failure | Store intent, retry on next boot |
Safe mode: device blinks a fault code, creates a Wi-Fi AP for technician diagnostics, and refuses to operate until provisioned.
Inventory and traceability
Maintain a device registry from factory to retirement:
serial → claim_cert_fp → operational_cert_fp → thing_arn →
factory_date → firmware_version → customer → deployed_location → status
This enables targeted certificate rotation, firmware updates, and recall if a batch has a hardware defect.
Zero-touch provisioning flow
Device boots → claims certificate via factory cert → cloud assigns identity → receives config blob
Track provisioning state: factory, claimed, active, retired. Never reuse device IDs — retired devices stay in denylist.
Common production mistakes
Teams get device provisioning fleet wrong in predictable ways:
- Skipping failure-mode rehearsal — run a game day or fault injection exercise before peak traffic, not after the first outage.
- Missing correlation context — every error path should carry request, trace, or tenant identifiers so incidents are debuggable.
- Optimizing for demo, not steady state — load tests, cache warm-up, and cold-start paths matter more than local dev latency.
- Undocumented trade-offs — if you chose speed over strict correctness (or vice versa), write that down for the next engineer.
IoT deployments of device provisioning fleet fail in the field when firmware assumes stable Wi-Fi, OTA rollback is untested, and device certificates expire without automated renewal.
Debugging and triage workflow
When device provisioning fleet misbehaves in production, work top-down instead of guessing:
- Confirm scope — one tenant, region, or deployment stage? Narrow blast radius before deep diving.
- Check recent changes — deploys, flag flips, config pushes, and schema migrations in the last 24 hours.
- Compare golden signals — latency, error rate, saturation, and traffic for the affected surface vs. baseline.
- Reproduce minimally — smallest input or scenario that triggers the failure; capture traces/logs with correlation IDs.
- Fix forward or rollback — if rollback is faster than root-cause during incident, rollback first, postmortem second.
- Add a guard — alert, integration test, or circuit breaker so the same class of failure is caught earlier next time.
Document the timeline during triage. Future you (and on-call) will need timestamps, not just conclusions.
Resources
- AWS IoT Fleet Provisioning — just-in-time and just-in-time provisioning templates
- Azure IoT Hub Device Provisioning Service — zero-touch provisioning for Azure
- FIDO Device Onboard (FDO) — emerging standard for automatic IoT onboarding
- ESP-IDF Provisioning Guide — Espressif's Wi-Fi and BLE provisioning for ESP32
Frequently asked questions
What is zero-touch provisioning for IoT devices?
Zero-touch provisioning means a device authenticates and registers itself with the cloud on first boot without manual configuration. The device uses a factory-installed claim certificate or hardware identity (TPM, secure element) to prove itself to a provisioning service, which assigns a unique identity, credentials, and configuration. No technician enters Wi-Fi passwords or API keys.
What is the difference between claim certificates and operational certificates?
A claim certificate is a shared factory credential with limited permissions — it can only call the provisioning API. Once the device receives its unique operational certificate, the claim certificate is revoked. Operational certificates have full device permissions (MQTT publish/subscribe, shadow updates) and are unique per device.
How do I handle provisioning failures in the factory?
Build a factory test station that verifies: device boots, connects to provisioning endpoint, receives operational certificate, connects to production MQTT, publishes a test message, and receives a test command. Fail the unit and quarantine it if any step fails. Log the device serial, certificate fingerprint, and test results for traceability.
Hiring a senior Android / Flutter engineer?
I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.
Get in touch →