How to connect ESP32 to AWS IoT Core Using MQTT

How to Connect ESP32 to AWS IoT Core Using MQTT

When it comes to AWS IoT Core, one of the prerequisites for secure device-to-cloud communication is the use of Mutual TLS (mTLS). mTLS is a security protocol that facilitates a two-way handshake between the client (in this case, the ESP32 device) and the server (AWS IoT Core). Through this handshake, both parties verify each other's identity by exchanging digital certificates. This ensures that the data is encrypted during transit and that the device and server are precisely who they claim to be.

This tutorial will guide you through setting up an ESP32 to connect to AWS IoT Core using MQTT. We will leverage Lua as the programming language and the Xedge32 IDE as our development environment. This combination offers an accessible yet powerful way to interact with AWS IoT Core while ensuring the highest level of security through mTLS.


Before diving in, make sure you have completed the following prerequisite tutorials:

By building on the knowledge gained from these foundational tutorials, you'll be better equipped to follow along with this tutorial.

AWS IoT Core Quick Connect

We provide a ready-to-run example designed for AWS IoT Core Quick Connect, a feature that enables devices to connect to AWS IoT Core with minimal setup and configuration. It is designed to simplify connecting devices to AWS IoT Core by automating the creation of a thing object, generating certificates, and establishing a secure connection between the device and AWS IoT Core. The feature is handy for prototyping and testing IoT solutions, allowing developers to quickly connect devices to AWS IoT Core.

The following Amazon video tutorial is designed for Python, but it is also applicable to Lua. Follow the video instructions until you get to the section where you have downloaded the file, which contains essential elements for connecting to AWS IoT Core.

Once you've downloaded the file, you are ready to set up the Lua program. Follow these steps:

  1. Clone the LSP-Examples GitHub repo.
  2. Navigate to the directory: LSP-Examples/AWS-MQTT/www/.
  3. Download AmazonRootCA1.pem and save the certificate in the www directory.
  4. Unzip the in a separate directory and copy the two files Demo_Thing.cert.pem and Demo_Thing.private.key to the LSP-Examples/AWS-MQTT/www/ directory.
  5. Open (from the ZIP file) in an editor; copy the MQTT broker name (the name is after the -e option).
  6. Open www/.preload in a source code editor; Set the broker name in the empty string: awsBroker = "" and save the file.
  7. Using a browser, navigate to the ESP32 using URL http://ip-addr/rtl/apps/disk/ to access the Web File Manager.
  8. Click the plus sign in the web file manager to create a new directory; enter the directory name amazon and click OK.
  9. Using the Web File Manager, click the new amazon directory to navigate into it.
  10. Using your file manager, select the four files in the 'www' directory and drop the files onto the Web File Manager Window in your browser to upload the files to the ESP32.
  11. Navigate to http://ip-addr/rtl/.
  12. Expand disk in the left pane, right click on amazon, and select 'New App'.
  13. In the "Application Configuration" dialog window click the run button and then click the save button.

Xedge32 features a Web File Manager that simplifies the process of uploading files to your ESP32. The screenshot below illustrates how to select the four necessary files with your computer's native file manager and then drag and drop them into the Web File Manager window in your browser.

ESP32 Web File Manager

Figure 1: Xedge's integrated Web File Manager and WebDAV server

After clicking the 'Save' button in Step 13, if everything is configured correctly, your ESP32 should successfully connect to AWS IoT Core. Below, a screenshot of the Xedge32 IDE displays both the AWS IoT Core MQTT Lua program and the console. The console will output information generated by the running example.

AWS IoT Core MQTT Example

Figure 2: Xedge IDE showing the AWS IoT Core MQTT Lua code and the console output

The example will run indefinitely until you either power off the ESP32 or deactivate the Lua program through the Application Configuration dialog. Below is a screenshot showing the program status immediately after turning the Lua program off. Note that in the left pane, the 'amazon' app icon within the tree menu will appear green when the program is active and gray when it's turned off.

Stopping AWS IoT Core MQTT Example

Figure 3: Toggling the app's on/off state using the Application Configuration dialog

How Does the MQTT AWS Lua Program Work?

This article's AWS IoT Core MQTT example program was initially designed for the Mako Server. As such, it does not include specific interactions with ESP32 hardware, like sending a button state via MQTT. We recommend reading Mako Server's AWS IoT Core MQTT tutorial to understand how this AWS IoT Core Lua program operates. The Mako Server tutorial explains mTLS in greater detail and how the ALPN TLS extension enables us to connect to the MQTT broker using HTTPS port 443.

To enhance the generic AWS IoT Core MQTT example with ESP32-specific features, you can append the following code snippet at the end of the example code in the Xedge32 IDE:

local cfg={
      if mqtt then
pin15 = esp32.gpio(15,"IN", cfg)

This modified code is adapted from the GPIO Example 4: Using Interrupt Callback Function example in the Xedge32 documentation, but with added MQTT functionality. If you connect a push button to pin 15 of the ESP32, an MQTT message will be sent each time the button changes state.

Xedge32 ESP32 Button Diagram

Figure 4: Button wiring

If you find that the constant stream of published messages makes it difficult to read the console data, you can opt to disable the timer-based publishing feature. To do this, locate the following line in the example code:


Comment out this line by adding two dashes (--) in front of it. This will prevent the example from publishing messages at regular intervals, making it easier for you to monitor the console output when clicking the button.

After you have re-enabled the example, using the Application Configuration dialog as shown in Figure 3, you should now observe output similar to the following in the console whenever you change the state of the push button:

onpub 76: Received:	topic_1	{
  message = "Button",
  state = true

Reflecting on AWS IoT Core Complexities

Exploring AWS IoT Core reveals a platform that is both incredibly powerful and versatile, designed to accommodate a wide range of IoT applications. However, it also comes with its complexities, making the learning curve steep for those new to the ecosystem. One specific nuance to be aware of is how AWS IoT Core Quick Connect handles Access Control Lists (ACLs). AWS IoT Core Quick Connect automatically configures ACLs for two MQTT topics: "topic_1" and "topic_2". If you attempt to subscribe to or publish to topics outside of these predefined ones, the AWS broker will terminate the connection abruptly. This security feature is in place to ensure that only authorized operations are carried out, further reinforcing the robustness of the AWS IoT Core platform.

Consider beginning with the Mako Server for the initial AWS IoT Core development process. Often referred to as the "big brother" of Xedge32, the Mako Server offers a more approachable environment for initial advanced IoT development. Moreover, the Lua program we explored in this article was initially designed for the Mako Server, yet, as demonstrated, it's equally effective on Xedge32.

Certificate Management for Large-Scale Deployments

In a large-scale deployment, managing keys and certificates efficiently and securely is crucial. Each device must have a unique identity verified by the AWS IoT Certificate Authority (CA). This section will guide you through generating keys and Certificate Signing Requests (CSRs) directly on the ESP32 devices, which can then be used to obtain certificates from AWS IoT Core.

Generating Keys

To ensure the security of each device, start by generating a unique key pair on the device itself. The ba.create.key function allows you to create an ECC (Elliptic Curve Cryptography) key, which is recommended due to its strong security and efficiency on embedded devices.

-- Generate an ECC key
local eccKey = ba.create.key()

Creating a Certificate Signing Request (CSR)

Once you have the private key, you can create a CSR using the ba.create.csr function. The CSR contains the device's public key and identity information, which AWS IoT will use to generate a signed certificate.

-- Define the Distinguished Name (DN) table
local dn = {
  commonname = "",
  organization = "Your Organization",
  unit = "IoT Division",
  locality = "Your City",
  province = "Your State",
  countryname = "XX", -- Replace XX with your country's ISO code
  email = ""

-- Create a CSR for the device
local csr = ba.create.csr(eccKey, dn, nil, { "SSL_CLIENT" },

Sending the CSR to AWS IoT using HTTP Client

With the CSR generated, you can now submit it to AWS IoT Core using the AWS IoT CreateCertificateFromCsr API to obtain a device certificate.

Prepare the HTTP Client: Before you can send the CSR, you need to set up the HTTP client with the necessary headers and AWS credentials. AWS requires that requests to its services are signed with AWS Signature Version 4.

local httpc = require('httpc')
-- Configure the HTTP client for AWS IoT
local client = httpc.create()

   -- Replace 'region' with your AWS region
   url = "https://iot.'region'",
   method = "POST",
   headers = {
      ["Content-Type"] = "application/x-amz-json-1.0",
      ["x-amz-target"] = "CreateCertificateFromCsr",
      ["Host"] = "iot.'region'" -- Replace 'region' with your AWS region
      -- Add AWS Signature Version 4 headers here
      -- This will involve creating a signing key and adding the 'Authorization' header
      -- The details of this process are beyond the scope of this example

Create the Request Payload: The CSR must be sent as a JSON payload in the body of the request. You must encode the CSR in Base64 and include it in the payload.

-- Create the JSON payload
local payload = {
   certificateSigningRequest = ba.b64encode(csr)

-- Send the CSR
-- Wait for AWS response
local respPayload,err = client:read("*a")
if respPayload then
   local certificate = ba.json.decode(respPayload)
   if certificate then
      -- Save certificate
      -- JSON error
   -- AWS resp error. Check client:status()


Using the HTTP client, you can programmatically send the CSR to AWS IoT and receive a signed certificate in response. This process is a cornerstone for registering devices in a secure and scalable manner, which is especially critical for large-scale IoT deployments.

AWS IoT's Just-in-Time Registration (JITR) feature simplifies the registration process for devices that have yet to be pre-configured. JITR allows devices to be registered automatically when they first connect to AWS IoT. When a device with a certificate signed by a registered CA connects to AWS IoT, the service can use the certificate to authenticate the device and register it on the fly. By leveraging JITR, you can automate the deployment of new devices without requiring manual registration steps. This allows devices to be manufactured without AWS IoT-specific configurations and still securely connect to your AWS IoT environment when powered on for the first time in the field.

We welcome your thoughts and discussions on this article. While our site does not host a comment section, please feel free to comment on LinkedIn: Learn how to master Mutual TLS.

What's Next:

With this foundation, you're well on your way to becoming an expert in mTLS. To continue building on what you've learned, don't miss our curated list of Xedge32 tutorials.

Need a helping hand?

Unlock the potential of embedded web servers and IoT with our curated tutorials. They're your DIY toolkit for success. But if you're racing against the clock or need a deeper perspective, our expert consulting services are your ace in the hole. With Real Time Logic, you're never alone on your DIY journey. Let's collaborate and make magic happen!


OPC-UA Client & Server

An easy to use OPC UA stack that enables bridging of OPC-UA enabled industrial products with cloud services, IT, and HTML5 user interfaces.

Edge Controller

Edge Controller

Use our user programmable Edge-Controller as a tool to accelerate development of the next generation industrial edge products and to facilitate rapid IoT and IIoT development.

On-Premises IoT

On-Premises IoT Platform

Learn how to use the Barracuda App Server as your On-Premises IoT Foundation.

Embedded Web Server

Barracuda Embedded Web Server

The compact Web Server C library is included in the Barracuda App Server protocol suite but can also be used standalone.

WebSocket Server

Microcontroller Friendly

The tiny Minnow Server enables modern web server user interfaces to be used as the graphical front end for tiny microcontrollers. Make sure to check out the reference design and the Minnow Server design guide.

WebDAV Server

Network File System

Why use FTP when you can use your device as a secure network drive.

HTTP Client

Secure HTTP Client Library

PikeHTTP is a compact and secure HTTP client C library that greatly simplifies the design of HTTP/REST style apps in C or C++.

WebSocket Client

Microcontroller Friendly

The embedded WebSocket C library lets developers design tiny and secure IoT applications based on the WebSocket protocol.

SMTP Client

Secure Embedded SMTP Library

Send alarms and other notifications from any microcontroller powered product.

Crypto Library

RayCrypto C Library

The RayCrypto engine is an extremely small and fast embedded crypto library designed specifically for embedded resource-constrained devices.

Embedded PKI Service

Automatic SSL Certificate Management for Devices

Real Time Logic's SharkTrust™ service is an automatic Public Key Infrastructure (PKI) solution for products containing an Embedded Web Server.


Modbus TCP client

The Modbus client enables bridging of Modbus enabled industrial products with modern IoT devices and HTML5 powered HMIs.

Posted in Xedge32