In this project, you’ll create a web page that displays sensor readings in a plot that you can access from anywhere in the world. In summary, you’ll build an ESP32 or ESP8266 client that makes a request to a PHP script to publish sensor readings in a MySQL database.

As an example, we’ll be using a BME280 sensor connected to an ESP board. You can modify the code provided to send readings from a different sensor or use multiple boards.
To create this project, you’ll use these technologies:
You might also find helpful reading these projects:
Table of Contents
The project is divided into the following main sections:
To see how the project works, you can watch the following video demonstration:
The goal of this project is to have your own domain name and hosting account that allows you to store sensor readings from the ESP32 or ESP8266. You can visualize the readings from anywhere in the world by accessing your own server domain. Here’s a high level overview of the project:
I recommend using one of the following hosting services that can handle all the project requirements:
Those two services are the ones that I use and personally recommend, but you can use any other hosting service. Any hosting service that offers PHP and MySQL will work with this tutorial. If you don’t have a hosting account, I recommend signing up for Bluehost.
Get Hosting and Domain Name with Bluehost »
When buying a hosting account, you’ll also have to purchase a domain name. This is what makes this project interesting: you’ll be able to go your domain name (http://example.com) and see your ESP readings.
If you like our projects, you might consider signing up to one of the recommended hosting services, because you’ll be supporting our work.
Note: you can also run a LAMP (Linux, Apache, MySQL, PHP) server on a Raspberry Pi to access data in your local network. However, the purpose of this tutorial is to publish readings in your own domain name that you can access from anywhere in the world. This allows you to easily access your ESP readings without relying on a third-party IoT platform.
After signing up for a hosting account and setting up a domain name, you can login to your cPanel or similar dashboard. After that, follow the next steps to create your database, username, password and SQL table.
Open the “Advanced” tab:

1. Type “database” in the search bar and select “MySQL Database Wizard”.

2. Enter your desired Database name. In my case, the database name is esp_data. Then, press the “Next Step” button:

Note: later you’ll have to use the database name with the prefix that your host gives you (my database prefix in the screenshot above is blurred). I’ll refer to it as example_esp_data from now on.
3. Type your Database username and set a password. You must save all those details, because you’ll need them later to establish a database connection with your PHP code.

That’s it! Your new database and user were created successfully. Now, save all your details because you’ll need them later:
After creating your database and user, go back to cPanel dashboard and search for “phpMyAdmin”.

In the left sidebar, select your database name example_esp_data and open the “SQL” tab.

Important: make sure you’ve opened the example_esp_data database. Then, click the SQL tab. If you don’t follow these exact steps and run the SQL query, you might create a table in the wrong database.
Copy the SQL query in the following snippet:
CREATE TABLE Sensor ( id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, value1 VARCHAR(10), value2 VARCHAR(10), value3 VARCHAR(10), reading_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP )
Paste it in the SQL query field (highlighted with a red rectangle) and press the “Go” button to create your table:

After that, you should see your newly created table called Sensor in the example_esp_data database as shown in the figure below:
In this section, we’re going to create a PHP script that receives incoming requests from the ESP32 or ESP8266 and inserts the data into a MySQL database.
If you’re using a hosting provider with cPanel, you can search for “File Manager”:

Then, select the public_html option and press the “+ File” button to create a new .php file.

Note: if you’re following this tutorial and you’re not familiar with PHP or MySQL, I recommend creating these exact files. Otherwise, you’ll need to modify the ESP sketch provided with different URL paths.
Create a new file in /public_html with this exact name and extension: post-data.php

Edit the newly created file (post-data.php) and copy the following snippet:
<?php /* Rui Santos Complete project details at https://RandomNerdTutorials.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ $servername = "localhost"; // REPLACE with your Database name $dbname = "REPLACE_WITH_YOUR_DATABASE_NAME"; // REPLACE with Database user $username = "REPLACE_WITH_YOUR_USERNAME"; // REPLACE with Database user password $password = "REPLACE_WITH_YOUR_PASSWORD"; // Keep this API Key value to be compatible with the ESP32 code provided in the project page. If you change this value, the ESP32 sketch needs to match $api_key_value = "tPmAT5Ab3j7F9"; $api_key = $value1 = $value2 = $value3 = ""; if ($_SERVER["REQUEST_METHOD"] == "POST") { $api_key = test_input($_POST["api_key"]); if($api_key == $api_key_value) { $value1 = test_input($_POST["value1"]); $value2 = test_input($_POST["value2"]); $value3 = test_input($_POST["value3"]); // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "INSERT INTO Sensor (value1, value2, value3) VALUES ('" . $value1 . "', '" . $value2 . "', '" . $value3 . "')"; if ($conn->query($sql) === TRUE) { echo "New record created successfully"; } else { echo "Error: " . $sql . "<br>" . $conn->error; } $conn->close(); } else { echo "Wrong API Key provided."; } } else { echo "No data posted with HTTP POST."; } function test_input($data) { $data = trim($data); $data = stripslashes($data); $data = htmlspecialchars($data); return $data; }
Before saving the file, you need to modify the $dbname, $username and $password variables with your unique details:
// Your Database name $dbname = "example_esp_data"; // Your Database user $username = "example_esp_board"; // Your Database user password $password = "YOUR_USER_PASSWORD";
After adding the database name, username and password, save the file and continue with this tutorial. If you try to access your domain name in the next URL path, you’ll see the message:
http://example.com/post-data.php

Create another PHP file in the /public_html directory that will plot the database content in a chart on a web page. Name your new file: esp-chart.php

Edit the newly created file (esp-chart.php) and copy the following code:
<!-- Rui Santos Complete project details at https://RandomNerdTutorials.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. --> <?php $servername = "localhost"; // REPLACE with your Database name $dbname = "REPLACE_WITH_YOUR_DATABASE_NAME"; // REPLACE with Database user $username = "REPLACE_WITH_YOUR_USERNAME"; // REPLACE with Database user password $password = "REPLACE_WITH_YOUR_PASSWORD"; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT id, value1, value2, value3, reading_time FROM Sensor order by reading_time desc limit 40"; $result = $conn->query($sql); while ($data = $result->fetch_assoc()){ $sensor_data[] = $data; } $readings_time = array_column($sensor_data, 'reading_time'); // ******* Uncomment to convert readings time array to your timezone ******** /*$i = 0; foreach ($readings_time as $reading){ // Uncomment to set timezone to - 1 hour (you can change 1 to any number) $readings_time[$i] = date("Y-m-d H:i:s", strtotime("$reading - 1 hours")); // Uncomment to set timezone to + 4 hours (you can change 4 to any number) //$readings_time[$i] = date("Y-m-d H:i:s", strtotime("$reading + 4 hours")); $i += 1; }*/ $value1 = json_encode(array_reverse(array_column($sensor_data, 'value1')), JSON_NUMERIC_CHECK); $value2 = json_encode(array_reverse(array_column($sensor_data, 'value2')), JSON_NUMERIC_CHECK); $value3 = json_encode(array_reverse(array_column($sensor_data, 'value3')), JSON_NUMERIC_CHECK); $reading_time = json_encode(array_reverse($readings_time), JSON_NUMERIC_CHECK); /*echo $value1; echo $value2; echo $value3; echo $reading_time;*/ $result->free(); $conn->close(); ?> <!DOCTYPE html> <html> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="https://code.highcharts.com/highcharts.js"></script> <style> body { min-width: 310px; max-width: 1280px; height: 500px; margin: 0 auto; } h2 { font-family: Arial; font-size: 2.5rem; text-align: center; } </style> <body> <h2>ESP Weather Station</h2> <div id="chart-temperature" class="container"></div> <div id="chart-humidity" class="container"></div> <div id="chart-pressure" class="container"></div> <script> var value1 = <?php echo $value1; ?>; var value2 = <?php echo $value2; ?>; var value3 = <?php echo $value3; ?>; var reading_time = <?php echo $reading_time; ?>; var chartT = new Highcharts.Chart({ chart:{ renderTo : 'chart-temperature' }, title: { text: 'BME280 Temperature' }, series: [{ showInLegend: false, data: value1 }], plotOptions: { line: { animation: false, dataLabels: { enabled: true } }, series: { color: '#059e8a' } }, xAxis: { type: 'datetime', categories: reading_time }, yAxis: { title: { text: 'Temperature (Celsius)' } //title: { text: 'Temperature (Fahrenheit)' } }, credits: { enabled: false } }); var chartH = new Highcharts.Chart({ chart:{ renderTo:'chart-humidity' }, title: { text: 'BME280 Humidity' }, series: [{ showInLegend: false, data: value2 }], plotOptions: { line: { animation: false, dataLabels: { enabled: true } } }, xAxis: { type: 'datetime', //dateTimeLabelFormats: { second: '%H:%M:%S' }, categories: reading_time }, yAxis: { title: { text: 'Humidity (%)' } }, credits: { enabled: false } }); var chartP = new Highcharts.Chart({ chart:{ renderTo:'chart-pressure' }, title: { text: 'BME280 Pressure' }, series: [{ showInLegend: false, data: value3 }], plotOptions: { line: { animation: false, dataLabels: { enabled: true } }, series: { color: '#18009c' } }, xAxis: { type: 'datetime', categories: reading_time }, yAxis: { title: { text: 'Pressure (hPa)' } }, credits: { enabled: false } }); </script> </body> </html>
After adding the $dbname, $username and $password save the file and continue with this project.
// Your Database name $dbname = "example_esp_data"; // Your Database user $username = "example_esp_board"; // Your Database user password $password = "YOUR_USER_PASSWORD";
If you try to access your domain name in the following URL path, you’ll see the following:
https://example.com/esp-chart.php
That’s it! If you see three empty charts in your browser, it means that everything is ready. In the next section, you’ll learn how to publish your ESP32 or ESP8266 sensor readings.
To build the charts, we’ll use the Highcharts library. We’ll create three charts: temperature, humidity and pressure over time. The charts display a maximum of 40 data points, and a new reading is added every 30 seconds, but you change these values in your code.
This project is compatible with both the ESP32 and ESP8266 boards. You just need to assemble a simple circuit and upload the sketch provided to insert temperature, humidity, pressure, and more into your database every 30 seconds. The sketch is slightly different for each board.

For this example, we’ll get sensor readings from the BME280 sensor. Here’s a list of parts you need to build the circuit for this project:
You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!
The BME280 sensor module we’re using communicates via I2C communication protocol, so you need to connect it to the ESP32 or ESP8266 I2C pins.
The ESP32 I2C pins are:
So, assemble your circuit as shown in the next schematic diagram (read complete Guide for ESP32 with BME280).

Recommended reading: ESP32 Pinout Reference Guide
The ESP8266 I2C pins are:
Assemble your circuit as in the next schematic diagram if you’re using an ESP8266 board (read complete Guide for ESP8266 with BME280).

Recommended reading: ESP8266 Pinout Reference Guide
We’ll program the ESP32/ESP8266 using Arduino IDE, so you must have the ESP32/ESP8266 add-on installed in your Arduino IDE. Follow one of the next tutorials depending on the board you’re using:
Follow this section if you’re using an ESP32. For an ESP8266 click here.
After installing the necessary board add-ons, copy the following code to your Arduino IDE, but don’t upload it yet. You need to make some changes to make it work for you.
/* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-mysql-database-php/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <WiFi.h> #include <WiFiClientSecure.h> #include <HTTPClient.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // REPLACE with your Domain name and URL path or IP address with path //const char* serverName = "https://example.com/post-data.php"; // Keep this API Key value to be compatible with the PHP code provided in the project page. // If you change the apiKeyValue value, the PHP file /post-esp-data.php also needs to have the same key String apiKeyValue = "tPmAT5Ab3j7F9"; /*#include <SPI.h> #define BME_SCK 18 #define BME_MISO 19 #define BME_MOSI 23 #define BME_CS 5*/ #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme; // I2C //Adafruit_BME280 bme(BME_CS); // hardware SPI //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI void setup() { Serial.begin(115200); WiFi.begin(ssid, password); Serial.println("Connecting"); while(WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to WiFi network with IP Address: "); Serial.println(WiFi.localIP()); // (you can also pass in a Wire library object like &Wire2) bool status = bme.begin(0x76); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring or change I2C address!"); while (1); } } void loop() { //Check WiFi connection status if(WiFi.status()== WL_CONNECTED){ WiFiClientSecure *client = new WiFiClientSecure; client->setInsecure(); //don't use SSL certificate HTTPClient https; // Your Domain name with URL path or IP address with path https.begin(*client, serverName); // Specify content-type header https.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Prepare your HTTP POST request data String httpRequestData = "api_key=" + apiKeyValue + "&value1=" + String(bme.readTemperature()) + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + ""; Serial.print("httpRequestData: "); Serial.println(httpRequestData); // You can comment the httpRequestData variable above // then, use the httpRequestData variable below (for testing purposes without the BME280 sensor) //String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14"; // Send HTTP POST request int httpResponseCode = https.POST(httpRequestData); // If you need an HTTP request with a content type: text/plain //https.addHeader("Content-Type", "text/plain"); //int httpResponseCode = https.POST("Hello, World!"); // If you need an HTTP request with a content type: application/json, use the following: //https.addHeader("Content-Type", "application/json"); //int httpResponseCode = https.POST("{\"value1\":\"19\",\"value2\":\"67\",\"value3\":\"78\"}"); if (httpResponseCode>0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); } else { Serial.print("Error code: "); Serial.println(httpResponseCode); } // Free resources https.end(); } else { Serial.println("WiFi Disconnected"); } //Send an HTTP POST request every 30 seconds delay(30000); }
You need to modify the following lines with your network credentials: SSID and password. The code is well commented on where you should make the changes.
// Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";
You also need to type your domain name, so the ESP publishes the readings to your own server.
const char* serverName = "https://example.com/post-data.php";
Now, you can upload the code to your board.
Note: Most servers require you to make HTTPS requests. The code above makes HTTPS requests to be compliant with the requirements of most cloud servers nowadays.
Your server doesn’t support HTTPS? Use this code instead.
This project is already quite long, so we won’t cover in detail how the code works, but here’s a quick summary:
Then, in the loop() is where you actually make the HTTP POST request every 30 seconds with the latest BME280 readings:
// Your Domain name with URL path or IP address with path http.begin(serverName); // Specify content-type header http.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Prepare your HTTP POST request data String httpRequestData = "api_key=" + apiKeyValue + "&value1=" + String(bme.readTemperature()) + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + ""; int httpResponseCode = http.POST(httpRequestData);
You can comment the httpRequestData variable above that concatenates all the BME280 readings and use the httpRequestData variable below for testing purposes:
String httpRequestData = "api_key=tPmAT5Ab3j7F9&value1=24.75&value2=49.54&value3=1005.14";
Learn more about HTTPS Requests with the ESP32: ESP32 HTTPS Requests (Arduino IDE).
Follow this section if you’re using an ESP8266. For an ESP32 check the section above.
After installing the necessary board add-ons and libraries, copy the following code to your Arduino IDE, but don’t upload it yet. You need to make some changes to make it work for you.
/* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-mysql-database-php/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <ESP8266WiFi.h> #include <ESP8266HTTPClient.h> #include <WiFiClientSecureBearSSL.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // REPLACE with your Domain name and URL path or IP address with path const char* serverName = "https://example.com/post-data.php"; // Keep this API Key value to be compatible with the PHP code provided in the project page. // If you change the apiKeyValue value, the PHP file /post-esp-data.php also needs to have the same key String apiKeyValue = "tPmAT5Ab3j7F9"; /*#include <SPI.h> #define BME_SCK 18 #define BME_MISO 19 #define BME_MOSI 23 #define BME_CS 5*/ #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme; // I2C //Adafruit_BME280 bme(BME_CS); // hardware SPI //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI void setup() { Serial.begin(115200); WiFi.begin(ssid, password); Serial.println("Connecting"); while(WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to WiFi network with IP Address: "); Serial.println(WiFi.localIP()); // (you can also pass in a Wire library object like &Wire2) bool status = bme.begin(0x76); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring or change I2C address!"); while (1); } } void loop() { //Check WiFi connection status if(WiFi.status()== WL_CONNECTED){ std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure); // Ignore SSL certificate validation client->setInsecure(); //create an HTTPClient instance HTTPClient https; // Your Domain name with URL path or IP address with path https.begin(*client, serverName); // Specify content-type header https.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Prepare your HTTP POST request data String httpRequestData = "api_key=" + apiKeyValue + "&value1=" + String(bme.readTemperature()) + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + ""; Serial.print("httpRequestData: "); Serial.println(httpRequestData); // You can comment the httpRequestData variable above // then, use the httpRequestData variable below (for testing purposes without the BME280 sensor) //String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14"; // Send HTTP POST request int httpResponseCode = https.POST(httpRequestData); // If you need an HTTP request with a content type: text/plain //http.addHeader("Content-Type", "text/plain"); //int httpResponseCode = https.POST("Hello, World!"); // If you need an HTTP request with a content type: application/json, use the following: //http.addHeader("Content-Type", "application/json"); //int httpResponseCode = https.POST("{\"value1\":\"19\",\"value2\":\"67\",\"value3\":\"78\"}"); if (httpResponseCode>0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); } else { Serial.print("Error code: "); Serial.println(httpResponseCode); } // Free resources https.end(); } else { Serial.println("WiFi Disconnected"); } //Send an HTTP POST request every 30 seconds delay(30000); }
You need to modify the following lines with your network credentials: SSID and password. The code is well commented on where you should make the changes.
// Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";
You also need to type your domain name, so the ESP publishes the readings to your own server.
const char* serverName = "https://example.com/post-data.php";
Now, you can upload the code to your board.
rnt_box type=”3″] Most servers require you to make HTTPS requests. The code above makes HTTPS requests to be compliant with the requirements of most cloud servers nowadays.
Your server doesn’t support HTTPS? Use this code instead.
Learn more about HTTPS Requests with the ESP8266: ESP8266 NodeMCU HTTPS Requests (Arduino IDE).
After completing all the steps, let your ESP board collect some readings and publish them to your server.

If everything is correct, this is what you should see in your Arduino IDE Serial Monitor:

If you open your domain name in this URL path:
https://example.com/esp-chart.php
You should see all the readings stored in your database. Refresh the web page to see the latest readings:

You can also go to phpMyAdmin to manage the data stored in your Sensor table. You can delete it, edit, etc…

Copyright ©2025. All Rights Reserved Emblab THE RAVE INNOVATION