Connecting ESP32 to AWS IoT Core: A Production Setup Guide
Most AWS IoT tutorials skip the hard parts — certificates, device shadows, reconnect logic. Here's the production setup that survives in the field.
AWS IoT Core is the right backend for production IoT fleets — managed MQTT broker, device shadows, rules engine, and deep AWS ecosystem integration. But the official getting-started guides gloss over the parts that actually break in production: certificate management, reconnect storms, and device shadow sync.
Architecture Overview
1ESP32 Device
2 │
3 │ MQTT over TLS (port 8883)
4 │ X.509 mutual authentication
5 ▼
6AWS IoT Core (managed MQTT broker)
7 │
8 ├── Device Shadow ──► DynamoDB (state persistence)
9 ├── Rules Engine ──► Lambda / S3 / Timestream (analytics)
10 └── Fleet Hub ──► CloudWatch (monitoring)Each device authenticates with a unique X.509 certificate. No username/password. No shared secrets. The private key never leaves the device.
Step 1: Certificate Provisioning
Generate and register a device certificate. Do this per device, per production unit:
1# Generate private key and CSR on your provisioning workstation
2openssl genrsa -out device_001.key 2048
3openssl req -new -key device_001.key \
4 -out device_001.csr \
5 -subj "/CN=device-001/O=AnalogData/C=IN"
6
7# Register with AWS IoT and sign
8aws iot create-certificate-from-csr \
9 --certificate-signing-request file://device_001.csr \
10 --set-as-active \
11 --query 'certificatePem' \
12 --output text > device_001.crt
13
14# Attach a policy that allows this device to publish/subscribe
15aws iot attach-policy \
16 --policy-name ESP32DevicePolicy \
17 --target <certificate-arn>
18
19# Register the thing
20aws iot create-thing --thing-name device-001
21aws iot attach-thing-principal \
22 --thing-name device-001 \
23 --principal <certificate-arn>Store the device_001.key and device_001.crt in ESP32's NVS (non-volatile storage), not in flash as header files — NVS allows certificate rotation via OTA.
Step 2: ESP-IDF MQTT Client with TLS
1#include "mqtt_client.h"
2#include "esp_tls.h"
3#include "nvs_flash.h"
4
5// Load certificates from NVS at runtime (not compiled-in)
6static char device_cert[2048];
7static char device_key[2048];
8static const char aws_root_ca[] =
9 "-----BEGIN CERTIFICATE-----\n"
10 /* Amazon Root CA 1 — embed this, it doesn't rotate */
11 "-----END CERTIFICATE-----\n";
12
13esp_mqtt_client_handle_t mqtt_client = NULL;
14
15void mqtt_init(void) {
16 load_cert_from_nvs("device_cert", device_cert, sizeof(device_cert));
17 load_cert_from_nvs("device_key", device_key, sizeof(device_key));
18
19 esp_mqtt_client_config_t mqtt_cfg = {
20 .broker = {
21 .address.uri = "mqtts://YOUR-ENDPOINT.iot.ap-south-1.amazonaws.com:8883",
22 .verification.certificate = aws_root_ca,
23 },
24 .credentials = {
25 .authentication = {
26 .certificate = device_cert,
27 .key = device_key,
28 },
29 },
30 .session = {
31 .keepalive = 60,
32 .disable_clean_session = true, // retain subscriptions across reconnects
33 .last_will = {
34 .topic = "$aws/things/device-001/shadow/update",
35 .msg = "{\"state\":{\"reported\":{\"online\":false}}}",
36 .qos = 1,
37 .retain = false,
38 },
39 },
40 .network = {
41 .timeout_ms = 10000,
42 .reconnect_timeout_ms = 5000, // backoff handled in event handler
43 },
44 };
45
46 mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
47 esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID,
48 mqtt_event_handler, NULL);
49 esp_mqtt_client_start(mqtt_client);
50}Step 3: Reconnect Logic That Doesn't Storm the Broker
Naive reconnect causes broker overload when hundreds of devices reconnect simultaneously after a network outage. Use exponential backoff with jitter:
1static int reconnect_attempts = 0;
2
3static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
4 int32_t event_id, void *event_data) {
5 esp_mqtt_event_handle_t event = event_data;
6
7 switch ((esp_mqtt_event_id_t)event_id) {
8 case MQTT_EVENT_CONNECTED:
9 ESP_LOGI(TAG, "Connected to AWS IoT Core");
10 reconnect_attempts = 0;
11 publish_device_shadow_online();
12 subscribe_to_commands();
13 break;
14
15 case MQTT_EVENT_DISCONNECTED:
16 reconnect_attempts++;
17 // Exponential backoff: 1s, 2s, 4s, 8s... max 60s + random jitter
18 int base_delay = (1 << MIN(reconnect_attempts, 6)) * 1000;
19 int jitter = esp_random() % 5000; // 0-5s random jitter
20 int delay = MIN(base_delay + jitter, 65000);
21
22 ESP_LOGW(TAG, "Disconnected. Reconnecting in %dms (attempt %d)",
23 delay, reconnect_attempts);
24 vTaskDelay(pdMS_TO_TICKS(delay));
25 esp_mqtt_client_reconnect(mqtt_client);
26 break;
27
28 case MQTT_EVENT_DATA:
29 handle_incoming_message(event->topic, event->topic_len,
30 event->data, event->data_len);
31 break;
32
33 default:
34 break;
35 }
36}Step 4: Device Shadow for State Synchronisation
Device Shadow is AWS IoT's mechanism for keeping device state in sync even when the device is offline:
1#define SHADOW_UPDATE_TOPIC "$aws/things/device-001/shadow/update"
2#define SHADOW_GET_TOPIC "$aws/things/device-001/shadow/get"
3#define SHADOW_DELTA_TOPIC "$aws/things/device-001/shadow/update/delta"
4
5void publish_telemetry(float temperature, float humidity) {
6 char payload[256];
7 snprintf(payload, sizeof(payload),
8 "{"
9 "\"state\":{"
10 " \"reported\":{"
11 " \"temperature\":%.2f,"
12 " \"humidity\":%.2f,"
13 " \"firmware\":\"1.2.3\","
14 " \"online\":true"
15 " }"
16 "}"
17 "}",
18 temperature, humidity);
19
20 esp_mqtt_client_publish(mqtt_client, SHADOW_UPDATE_TOPIC,
21 payload, 0, 1, 0);
22}
23
24// Handle desired state changes from cloud (e.g. config update)
25void handle_shadow_delta(const char *payload, int len) {
26 // Parse JSON delta and apply settings
27 // e.g. {"state":{"sampling_rate":10,"alert_threshold":0.025}}
28 cJSON *root = cJSON_ParseWithLength(payload, len);
29 cJSON *state = cJSON_GetObjectItem(root, "state");
30
31 int new_rate = cJSON_GetNumberValue(cJSON_GetObjectItem(state, "sampling_rate"));
32 if (new_rate > 0) update_sampling_rate(new_rate);
33
34 cJSON_Delete(root);
35
36 // Report the new desired state as reported to close the delta
37 publish_shadow_reported_state();
38}Key Takeaways
- One certificate per device — never share certificates across a fleet
- Store certs in NVS — not as compiled-in header files — enables OTA rotation
- Exponential backoff + jitter on reconnect — prevents reconnect storms at scale
- Device Shadow closes the offline gap — cloud can push config changes, device applies them on reconnect
- Last Will message marks device offline — free health monitoring without polling
Go from Arduino to Production Firmware
The ESP32-IDF Workshop covers ESP-IDF from scratch — tasks, queues, OTA, Wifi management, and deploying firmware that doesn't break at 3am.
Frequently Asked Questions
Quick answers to common questions

I build things that run on chips and the software that talks to them. ESP32, STM32, FreeRTOS, FastAPI, TinyML — from bare-metal firmware to cloud backends to on-device inference. Based in Bengaluru. Founder of Analog Data.