Connecting your ESP32 to AWS IoT Core over a secure MQTT channel is one of the most practical skills in production IoT development. This guide walks you through every step — from creating your AWS Thing to publishing sensor data and reading Device Shadow state — with complete, working Arduino code.

// What you'll build: A secure ESP32 → AWS IoT Core MQTT pipeline over TLS 1.2 with X.509 certificate authentication. The ESP32 will publish sensor data every 10 seconds and respond to Device Shadow desired-state changes in real time.
// AD SLOT — IN-CONTENT RESPONSIVE

Prerequisites

Before starting, make sure you have the following ready:

  • ESP32 development board (any variant: DevKit, WROOM, WROVER)
  • Active AWS account (free tier is sufficient)
  • Arduino IDE 2.x or PlatformIO with ESP32 board support installed
  • Libraries: PubSubClient by Nick O'Leary, ArduinoJson by Benoît Blanchon
  • WiFi network credentials
LibraryVersionInstall via
PubSubClient2.8+Arduino Library Manager
ArduinoJson6.xArduino Library Manager
WiFiClientSecurebuilt-inESP32 Arduino core

Step 1 — Create an AWS IoT Thing

Log into the AWS Console and navigate to IoT Core → Manage → Things. Click Create things → Create a single thing.

  1. Enter a Thing name — e.g. ESP32_Device_01
  2. Leave Thing type and groups as default for now
  3. Click Next to proceed to certificate creation

Step 2 — Generate Certificates

On the certificate page select "Auto-generate a new certificate". AWS will generate three files — download all of them before clicking Done, as you cannot download them again.

  • xxxx-certificate.pem.crt — your device certificate
  • xxxx-private.pem.key — your private key
  • AmazonRootCA1.pem — the AWS root CA
// Warning: The private key is shown only once. Download it immediately and store it securely. Never commit it to a public git repository.

Step 3 — Attach a Policy

Before activating the certificate you need to attach an IoT Policy. Create a new policy under Security → Policies with the following JSON. This grants your device permission to connect, publish, subscribe, and receive on any topic under its Thing name.

// AWS IoT Policy (JSON)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect",
        "iot:Publish",
        "iot:Subscribe",
        "iot:Receive"
      ],
      "Resource": "arn:aws:iot:YOUR_REGION:YOUR_ACCOUNT_ID:*"
    }
  ]
}
// Production note: For production, narrow the Resource ARN to specific topics per device. Wildcards are fine for development only.

Step 4 — Find Your Endpoint

In AWS IoT Core, go to Settings. Copy your Device data endpoint. It looks like:

xxxxxxxxxxxxxxx-ats.iot.us-east-1.amazonaws.com

This is your MQTT broker address. Save it — you'll need it in the sketch.

// AD SLOT — IN-CONTENT MID ARTICLE

Step 5 — Prepare the Certificates for Arduino

You need to embed the three certificate files as C string literals in your sketch. Open each .pem file in a text editor. The format for the certificate in your Arduino .h file is:

// secrets.h — Certificate header file
// secrets.h
// NEVER commit this file to a public repository!

#define AWS_IOT_ENDPOINT    "xxxxxxxxxxxxxxx-ats.iot.us-east-1.amazonaws.com"
#define AWS_IOT_PORT        8883
#define WIFI_SSID           "YourWiFiSSID"
#define WIFI_PASSWORD       "YourWiFiPassword"
#define THING_NAME          "ESP32_Device_01"

// Amazon Root CA 1
static const char AWS_CERT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
... (paste your AmazonRootCA1.pem content here)
-----END CERTIFICATE-----
)EOF";

// Device Certificate
static const char AWS_CERT_CRT[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
... (paste your xxxx-certificate.pem.crt content here)
-----END CERTIFICATE-----
)EOF";

// Device Private Key
static const char AWS_CERT_PRIVATE[] PROGMEM = R"EOF(
-----BEGIN RSA PRIVATE KEY-----
... (paste your xxxx-private.pem.key content here)
-----END RSA PRIVATE KEY-----
)EOF";

Step 6 — Full ESP32 Sketch

Create a new Arduino sketch and add the following code. This connects to WiFi, establishes a TLS connection to AWS IoT Core, publishes sensor data every 10 seconds, and subscribes to Device Shadow updates.

// main.ino — Full ESP32 AWS IoT Sketch
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include "secrets.h"

// MQTT Topics
#define PUBLISH_TOPIC    "$aws/things/" THING_NAME "/shadow/update"
#define SUBSCRIBE_TOPIC  "$aws/things/" THING_NAME "/shadow/update/delta"

WiFiClientSecure net;
PubSubClient client(net);

unsigned long lastPublish = 0;
const long PUBLISH_INTERVAL = 10000; // 10 seconds

// ----------------------------------------------------------
// Called whenever a message arrives on a subscribed topic
// ----------------------------------------------------------
void messageHandler(char* topic, byte* payload, unsigned int length) {
  Serial.print("[MQTT] Message on topic: ");
  Serial.println(topic);

  StaticJsonDocument<512> doc;
  deserializeJson(doc, payload, length);

  // Example: respond to desired LED state from Device Shadow
  if (doc["state"]["led"].is<bool>()) {
    bool ledState = doc["state"]["led"];
    digitalWrite(LED_BUILTIN, ledState ? HIGH : LOW);
    Serial.printf("[Shadow] LED set to: %s\n", ledState ? "ON" : "OFF");
  }
}

// ----------------------------------------------------------
// Connect to WiFi
// ----------------------------------------------------------
void connectWifi() {
  Serial.printf("[WiFi] Connecting to %s", WIFI_SSID);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.printf("\n[WiFi] Connected. IP: %s\n", WiFi.localIP().toString().c_str());
}

// ----------------------------------------------------------
// Connect to AWS IoT Core
// ----------------------------------------------------------
void connectAWS() {
  // Load certificates from PROGMEM
  net.setCACert(AWS_CERT_CA);
  net.setCertificate(AWS_CERT_CRT);
  net.setPrivateKey(AWS_CERT_PRIVATE);

  client.setServer(AWS_IOT_ENDPOINT, AWS_IOT_PORT);
  client.setCallback(messageHandler);
  client.setBufferSize(1024);

  Serial.print("[AWS] Connecting to IoT Core");
  while (!client.connected()) {
    if (client.connect(THING_NAME)) {
      Serial.println("\n[AWS] Connected!");
      client.subscribe(SUBSCRIBE_TOPIC);
      Serial.printf("[AWS] Subscribed to: %s\n", SUBSCRIBE_TOPIC);
    } else {
      Serial.printf(".\n[AWS] Failed, rc=%d. Retrying in 5s...\n", client.state());
      delay(5000);
    }
  }
}

// ----------------------------------------------------------
// Publish sensor data to Device Shadow
// ----------------------------------------------------------
void publishSensorData() {
  float temperature = 22.5 + random(-20, 20) / 10.0; // Simulated sensor
  float humidity    = 60.0 + random(-50, 50) / 10.0;

  StaticJsonDocument<256> doc;
  JsonObject state    = doc.createNestedObject("state");
  JsonObject reported = state.createNestedObject("reported");
  reported["temperature"] = serialized(String(temperature, 1));
  reported["humidity"]    = serialized(String(humidity, 1));
  reported["wifi_rssi"]   = WiFi.RSSI();
  reported["uptime_s"]    = millis() / 1000;

  char jsonBuffer[256];
  serializeJson(doc, jsonBuffer);

  if (client.publish(PUBLISH_TOPIC, jsonBuffer)) {
    Serial.printf("[MQTT] Published: %s\n", jsonBuffer);
  } else {
    Serial.println("[MQTT] Publish failed");
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  connectWifi();
  connectAWS();
}

void loop() {
  // Reconnect if connection dropped
  if (!client.connected()) {
    connectAWS();
  }
  client.loop();

  // Publish every PUBLISH_INTERVAL ms
  if (millis() - lastPublish > PUBLISH_INTERVAL) {
    publishSensorData();
    lastPublish = millis();
  }
}

Step 7 — Upload and Test

Select your ESP32 board in Arduino IDE (Tools → Board → ESP32 Dev Module), choose the correct COM port, and click Upload. Open the Serial Monitor at 115200 baud. You should see:

[WiFi] Connecting to YourSSID............
[WiFi] Connected. IP: 192.168.1.105
[AWS] Connecting to IoT Core
[AWS] Connected!
[AWS] Subscribed to: $aws/things/ESP32_Device_01/shadow/update/delta
[MQTT] Published: {"state":{"reported":{"temperature":"22.8","humidity":"61.2","wifi_rssi":-54,"uptime_s":12}}}

Step 8 — Verify in AWS Console

In AWS IoT Core, go to Test → MQTT test client. Subscribe to the topic # (wildcard). Within 10 seconds you should see the JSON payload from your ESP32 appear in the console.

To test Device Shadow control, publish the following to $aws/things/ESP32_Device_01/shadow/update:

// Publish this from AWS MQTT Test Client to toggle the LED
{
  "state": {
    "desired": {
      "led": true
    }
  }
}

The ESP32 will receive this via the /shadow/update/delta topic and turn its onboard LED on.

// How Device Shadow works: AWS IoT Device Shadow maintains a JSON document with desired (what the cloud wants) and reported (what the device actually has) states. When they differ, AWS publishes a delta message to your device automatically.
// AD SLOT — NEAR END OF ARTICLE

Troubleshooting

ErrorCauseFix
rc=-2 (connection refused)Wrong endpoint or portDouble-check endpoint from IoT Core Settings
rc=-4 (connection timeout)Port 8883 blocked by firewallTry network without firewall or use port 443
TLS handshake failedWrong or mismatched certificateEnsure all three certs are correct and activated
Policy deniedPolicy not attached to certificateAttach policy in IoT Core → Security → Certificates
Publish failsPayload too largeIncrease buffer: client.setBufferSize(2048)

Next Steps

Now that your ESP32 is connected securely to AWS IoT Core, here's what to build next:

  • AWS Lambda Rules Engine — trigger serverless functions when sensor values exceed a threshold
  • DynamoDB time-series storage — store all sensor readings for historical dashboards
  • Amazon SNS alerts — send SMS or email when temperature goes above a setpoint
  • OTA Updates — deliver firmware updates over the air using AWS IoT Jobs

If you found this guide helpful, consider sharing it with your team. For custom IoT development or consultancy, reach out to ESPSTACK.

A
Engr. Aamir Aziz Butt
PhD Researcher · IoT Engineer · Founder ESPSTACK

PhD candidate at Muslim Youth University, Islamabad. MS Computer Engineering from COMSATS. 10+ years of IoT development experience across ESP32, Jetson Nano, and cloud platforms. Published researcher with 7+ academic papers.