Building an ESP32 Web Server: The Complete Guide for Beginners

New to ESP32 web servers? You’re in the right place. In this detailed guide, we’ll cover the basic concepts you need to know to build web servers with the ESP32, so you can control and monitor its outputs remotely. We’ll start with the essential theory, then move on to practical examples to help you apply what you’ve learned.

Building an ESP32 Web Server: The Complete Guide for Beginners

We’ll cover creating a web server to control outputs and display sensor readings, adding authentication to the web server, setting the ESP32 as an access point, and more. To make things as simple as possible, we’ll use the built-in WebServer.h library.

New to the ESP32? We recommend following these getting-started guides first:

Tutorial Overview

In this tutorial, we’ll cover the following subjects. If you’re new to ESP32 web servers and you want to learn more about this subject, we recommend following these sections in order.

  1. Introducing Web Servers (Basic Concepts)
  2. ESP32 Web Server Example (Control Outputs)
  3. Add Authentication to your Web Server
  4. ESP32 Web Server (Access Point)
  5. ESP32 Web Server Example (Display Sensor Readings)

Recommended eBook: Build Web Servers with the ESP32 and ESP8266 (3rd edition)

Prerequisites

This tutorial focuses on programming the ESP32 using the Arduino core. Before proceeding, you should have the ESP32 Arduino core installed in your Arduino IDE. Follow the next tutorial to install the ESP32 on the Arduino IDE, if you haven’t already.


1) Introducing Web Servers

In simple terms, a web server is a “computer” that delivers web pages. It stores the website’s files, including HTML documents and related assets like images, CSS style sheets, fonts, and other files. When a user makes a request, the server sends those files to the user’s web browser.

When you access a web page in your browser, you’re actually sending a request to a server using the Hypertext Transfer Protocol (HTTP). This protocol handles how information is requested and delivered on the Internet. The server then responds by sending the web page you asked for—also through HTTP.

To better understand how all of this works with your ESP32 boards, let’s go over some terms you’ve probably heard before—but might not fully understand yet.

Request-response

Request-response is a message exchange pattern, in which a requestor (your browser) sends a request message to a replier system (the ESP32 as a web server) that receives and processes the request, and returns a message in response.

ESP32 Request Response

In most of our projects, the response message will be a web page that displays the latest sensor readings or that provides an interface to control outputs.

This is a simple, yet powerful messaging pattern, especially in client-server architectures.

Client-Server

When you type a URL in your browser, your device (the client) sends a request to a server using the Hypertext Transfer Protocol (HTTP). The server receives the request and responds—also via HTTP—by sending back the web page. This exchange between clients and servers happens over a computer network.

ESP32 Client Server Communication explained

In simple terms, clients make requests to servers. Servers handle the clients’ requests. 

Throughout this tutorial, we’ll treat the ESP32 as the server, and you—using your browser—as the client. In our projects, there’s only one server (the ESP32 board), but there can be multiple clients. These could be different web browsers on various devices like computers, smartphones, or tablets, all connected to the same network, or even multiple browser tabs opened on the same device.

IP Address

An IP address is a numerical label assigned to each device connected to a computer network.

It is a series of four values separated by periods, with each value ranging from 0 to 255. Here’s an example:

192.168.1.75

At home, your devices are connected to a private network through your router (local network). All devices connected to your router are part of your local network. Inside this network, each device has its own IP address.

Devices connected to the same router can access each other via the IP address. Devices outside your local network can’t access your local devices using their local IP address.

When you connect your ESP32 boards to your router, they become part of your local network. So, your ESP32 boards are assigned an IP address.

ESP32 Explaining IP Addresses

On your local network, the IP address of the ESP32 (and other devices) is assigned by the router using something called DHCP (Dynamic Host Configuration Protocol). You don’t need to worry about the details. You just need to know that DHCP automatically assigns an IP address and other network settings to each device on the network.

The router keeps track of every device on the network and maps an IP address to each device every time it joins the network. Two devices on the same network can’t have the same IP address.

Again, when the ESP32 is connected to your router, the IP address it gets is a local address. This means you can only access it from devices that are also connected to the same network. As shown in the previous image, you can access the ESP32 using a computer or smartphone that’s on the same network.

Wi-Fi Station and Wi-Fi Access Point

The ESP32 board can act as Wi-Fi Station, Access Point, or both.

Wi-Fi Station

When the ESP32 is set to work as a Wi-Fi station, it connects to an existing network, like your home router. In this setup, the router gives the ESP32 a unique local IP address. You can then communicate with the ESP32 from other devices (like your phone or computer) that are connected to the same network, simply by using that IP address.

ESP32 Wi-Fi Station Explained

Since the router is also connected to the internet, we can request information from the internet using our ESP32 boards like data from APIs, publish data to online platforms, use icons and images from the internet in our web server pages, or include JavaScript libraries. 

However, in some cases, we may not have a router nearby to connect the ESP32. In this scenario, you must set your ESP32 board as an access point.

Access Point

When your ESP32 is set up as an Access Point, other devices (such as your smartphone, tablet, or computer) can connect to it without the need for a router; the ESP controls its own Wi-Fi network.

ESP32 Access Point

Unlike a router, an ESP32 Access Point doesn’t connect further to a wired network or the Internet, so you can’t access external libraries, publish sensor readings to the cloud, or use services like mail. In most of our examples, we usually set the ESP32 boards as stations. You can easily modify our examples and set the boards as access points instead if that’s more suitable for your projects.

Client-Server Communication

There are several ways to communicate between the client and the server: HTTP Polling, Server-Sent Events (SSE), and WebSocket. We’ll focus on HTTP Polling, which is the easiest protocol to get started with and better understand how web servers work.

We have an eBook dedicated to web servers that explores in great detail those three communication protocols:

HTTP Polling

In HTTP polling, the client repeatedly asks the server for new information. When the server receives a request, it responds with the requested data. The server only sends information when the client asks for it.

Client-Server Communication HTTP Polling

For example, your browser sends a request to the ESP32, and it responds with whatever you’ve programmed it to provide, like a web page showing the latest sensor readings. To keep the web page updated using this method, the browser needs to keep sending requests at regular intervals.

ESP32 Web Server

Let’s take a look at a practical example with the ESP32 that acts as a web server in the local network.

Typically, a web server with the ESP32 in the local network looks like this: the ESP32 running as a web server is connected via Wi-Fi to your router. Your computer, smartphone, or tablet, are also connected to your router via Wi-Fi or Ethernet cable. So, the ESP32 and your browser are on the same network.

ESP32 Acting as a Web Server

When you type the ESP32 IP address in your browser, you are sending an HTTP request to your ESP32. Then, the ESP32 responds with a response that can contain a value, a reading, HTML text to display a web page, or any other data.

ESP32 Web Server Request Response

2) ESP32 Web Server Example (Control Outputs)

Based on what we’ve learned so far about web servers and the ESP32, how can you put it all together to build IoT projects? Since the ESP32 has GPIO pins, you can connect sensors, actuators, and other devices, and then control or monitor them through a web interface.

Here’s an example of a web server we’ve built to control an output. The following web page shows up when you enter the ESP32 IP address in a browser.

ESP32 Web Server - Control Outputs Example

When you press the ON button, the URL changes to the ESP IP address followed by /on. The ESP receives a request on that new URL, it checks which URL is being requested, and changes the LED state accordingly.

  • Press the ON button > request: /on > LED turns on

When you press the OFF button, a new request is made to the ESP32 in the /off URL. The ESP checks once again which URL is being requested and turns the LED off.

  • Press the OFF button > request: /off > LED turns off

The same concept can be applied to control multiple outputs.

Project Overview

Before going straight to the project, it is important to outline what our web server will do so that it is easier to follow and understand the steps later on.

  • The web server you’ll build controls two LEDs: one connected to the ESP32 GPIO 26, and another one to GPIO 27.
  • You can access the ESP32 web server by typing the ESP32 IP address in a browser on the local network.
  • By clicking the buttons on your web server, you can instantly change the state of each LED.

This is a simple example that illustrates how to build a web server that controls two LEDs. The idea is to replace those LEDs with a relay, or any other electronic components you want to control.

Wiring the Circuit

Start by building the circuit. Connect two LEDs to your ESP32 as shown in the following schematic diagram—with one LED connected to GPIO 26 and another to GPIO 27.

Here’s a list of parts you need to assemble the circuit:

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!

ESP32 Connected to Two LEDs

Recommended reading: ESP32 Pinout Reference: Which GPIO pins should you use?

Building the Web Server

After wiring the circuit, the next step is uploading the code to your ESP32. Copy the code below to your Arduino IDE, but don’t upload it yet. You need to make some changes to make it work.

/*  
  Rui Santos & Sara Santos - Random Nerd Tutorials
  https://RandomNerdTutorials.com/esp32-web-server-beginners-guide/
  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 <WebServer.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Assign output variables to GPIO pins
const int output26 = 26;
const int output27 = 27;
String output26State = "off";
String output27State = "off";

// Create a web server object
WebServer server(80);

// Function to handle turning GPIO 26 on
void handleGPIO26On() {
  output26State = "on";
  digitalWrite(output26, HIGH);
  handleRoot();
}

// Function to handle turning GPIO 26 off
void handleGPIO26Off() {
  output26State = "off";
  digitalWrite(output26, LOW);
  handleRoot();
}

// Function to handle turning GPIO 27 on
void handleGPIO27On() {
  output27State = "on";
  digitalWrite(output27, HIGH);
  handleRoot();
}

// Function to handle turning GPIO 27 off
void handleGPIO27Off() {
  output27State = "off";
  digitalWrite(output27, LOW);
  handleRoot();
}

// Function to handle the root URL and show the current states
void handleRoot() {
  String html = "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
  html += "<link rel=\"icon\" href=\"data:,\">";
  html += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}";
  html += ".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}";
  html += ".button2 { background-color: #555555; }</style></head>";
  html += "<body><h1>ESP32 Web Server</h1>";

  // Display GPIO 26 controls
  html += "<p>GPIO 26 - State " + output26State + "</p>";
  if (output26State == "off") {
    html += "<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>";
  } else {
    html += "<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>";
  }

  // Display GPIO 27 controls
  html += "<p>GPIO 27 - State " + output27State + "</p>";
  if (output27State == "off") {
    html += "<p><a href=\"/27/on\"><button class=\"button\">ON</button></a></p>";
  } else {
    html += "<p><a href=\"/27/off\"><button class=\"button button2\">OFF</button></a></p>";
  }

  html += "</body></html>";
  server.send(200, "text/html", html);
}

void setup() {
  Serial.begin(115200);

  // Initialize the output variables as outputs
  pinMode(output26, OUTPUT);
  pinMode(output27, OUTPUT);
  // Set outputs to LOW
  digitalWrite(output26, LOW);
  digitalWrite(output27, LOW);

  // Connect to Wi-Fi network
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  // Set up the web server to handle different routes
  server.on("/", handleRoot);
  server.on("/26/on", handleGPIO26On);
  server.on("/26/off", handleGPIO26Off);
  server.on("/27/on", handleGPIO27On);
  server.on("/27/off", handleGPIO27Off);

  // Start the web server
  server.begin();
  Serial.println("HTTP server started");
}

void loop() {
  // Handle incoming client requests
  server.handleClient();
}

View raw code

Setting Your Network Credentials

You have to modify the following lines with your network credentials: SSID and password.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = " REPLACE_WITH_YOUR_PASSWORD";

Getting the ESP32 IP Address

Now, you can upload the code, and it will work straight away. Don’t forget to check if you have the right board and COM port selected.

Open the Serial Monitor at a baud rate of 115200.

The ESP32 connects to Wi-Fi and prints its IP address on the Serial Monitor. Copy that IP address because you need it to access the ESP32 web server.

ESP32 Web Server - Get IP address on Serial Monitor

Note: If nothing shows up on the Serial Monitor, press the ESP32 “EN” button (ENABLE/RESET button next to the microUSB port).

Accessing the Web Server

Open your browser, paste the ESP32 IP address, and you’ll see the following page.

ESP32 Basic Web Server Example - control outputs

Testing the Web Server

Let’s test the web server. Click the button to turn GPIO 26 ON. You can see on the Serial Monitor that the ESP32 receives a request on the /26/on URL.

ESP32 Basic Web Server Example - control outputs

When the ESP receives that request, it turns the LED attached to GPIO 26 ON, and its state is also updated on the web page. Test the button for GPIO 27 and see that it works similarly.

ESP32 Basic Web Server - How it Works

You can also access the web server on your smartphone as long as it is connected to the same network.

How the Code Works

Now, let’s take a closer look at the code to see how it works.

The first thing you need to do is include the necessary libraries for Wi-Fi connectivity and set up a web server.

#include <WiFi.h>
#include <WebServer.h>

As mentioned previously, you need to insert your ssid and password in the following lines inside the double quotes.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = " REPLACE_WITH_YOUR_PASSWORD";

Assign GPIO pins to each of your outputs. In this example, we are using GPIO 26 and GPIO 27, but you can use any other suitable GPIOs.

const int output26 = 26;
const int output27 = 27;

Then, create variables to store the states of those outputs. You can add more outputs by defining additional variables.

String output26State = "off";
String output27State = "off";

Create a web server object on port 80 called server.

// Create a web server object
WebServer server(80);

setup()

Now, let’s go into the setup(). First, we start a serial communication at a baud rate of 115200 for debugging purposes.

Serial.begin(115200);

You also define your GPIOs as OUTPUTs and set them to LOW.

// Initialize the output variables as outputs
pinMode(output26, OUTPUT);
pinMode(output27, OUTPUT);
// Set outputs to LOW
digitalWrite(output26, LOW);
digitalWrite(output27, LOW);

The following lines begin the Wi-Fi connection with WiFi.begin(ssid, password), wait for a successful connection, and print the ESP32 IP address in the Serial Monitor.

// Connect to Wi-Fi network
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());

Read this guide to learn more about Wi-Fi with the ESP32: ESP32 Useful Wi-Fi Library Functions (Arduino IDE).

Finally, we set up the web server and define the routes it should handle. These routes will be requested when you click on the different buttons on the web page.

// Set up the web server to handle different routes
server.on("/", handleRoot);
server.on("/26/on", handleGPIO26On);
server.on("/26/off", handleGPIO26Off);
server.on("/27/on", handleGPIO27On);
server.on("/27/off", handleGPIO27Off);

// Start the web server
server.begin();
Serial.println("HTTP server started");

For example, when you make a request on the root / URL (you simply paste the ESP32 IP address on the web browser), it will run the handleRoot() function. When you click on the GPIO 26 ON button, it will make a request on the /26/on route and the board will run the handleGPIO26On() function, and so on… Those functions are defined at the beginning of the code before the setup(). We’ll take a look at them next.

loop()

In the loop(), we continuously listen for incoming client requests. This ensures that the ESP32 is always ready to respond to requests from your browser.

server.handleClient();

Handling GPIO Control

On the web page, you’ve seen that you have four buttons to control the GPIOs:

  • GPIO 26 ON button > request: /26/on > function: handleGPIO26On()
  • GPIO 26 OFF button > request: /26/off > function: handleGPIO26Off()
  • GPIO 27 ON button > request: /27/on > function: handleGPIO27On()
  • GPIO 27 OFF button > request: /27/off > function: handleGPIO27Off()

Let’s take a look at the GPIO 26 ON button. When you click that button, it makes a request to the ESP32 on the /26/on URL. When that happens, the handleGPIO26On() function will run.

// Function to handle turning GPIO 26 on
void handleGPIO26On() {
  output26State = "on";
  digitalWrite(output26, HIGH);
  handleRoot();
}

That function updates the state of the GPIO on the output26State variable and turns the GPIO on. Finally, it calls the handleRoot() function to display the web page with the right GPIO state.

This works similarly to the other buttons and corresponding routes and functions. Notice that all of these functions call the handleRoot() function to display the web page with the right GPIO states.

// Function to handle turning GPIO 26 off
void handleGPIO26Off() {
  output26State = "off";
  digitalWrite(output26, LOW);
  handleRoot();
}

// Function to handle turning GPIO 27 on
void handleGPIO27On() {
  output27State = "on";
  digitalWrite(output27, HIGH);
  handleRoot();
}

// Function to handle turning GPIO 27 off
void handleGPIO27Off() {
  output27State = "off";
  digitalWrite(output27, LOW);
  handleRoot();
}

Displaying the Web Page

The handleRoot() function is responsible for generating the web page. It sends the HTML and CSS needed to build the page. The HTML and CSS text required to build the web page are saved in the html variable.

// Function to handle the root URL and show the current states
void handleRoot() {
  String html = "<!DOCTYPE html><html><head><meta name=\"viewport\"
                 content=\"width=device-width, initial-scale=1\">";
  html += "<link rel=\"icon\" href=\"data:,\">";
  html += "<style>html { font-family: Helvetica; display: inline-block; 
           margin: 0px auto; text-align: center;}";
  html += ".button { background-color: #4CAF50; border: none; color: white; 
           padding: 16px 40px; text-decoration: none; font-size: 30px; 
           margin: 2px; cursor: pointer;}";
  html += ".button2 { background-color: #555555; }</style></head>";
  html += "<body><h1>ESP32 Web Server</h1>";

  // Display GPIO 26 controls
  html += "<p>GPIO 26 - State " + output26State + "</p>";
  if (output26State == "off") {
    html += "<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>";
  } else {
    html += "<p><a href=\"/26/off\"><button 
             class=\"button button2\">OFF</button></a></p>";
  }

  // Display GPIO 27 controls
  html += "<p>GPIO 27 - State " + output27State + "</p>";
  if (output27State == "off") {
    html += "<p><a href=\"/27/on\"><button class=\"button\">ON</button></a></p>";
  } else {
    html += "<p><a href=\"/27/off\"><button 
             class=\"button button2\">OFF</button></a></p>";
  }

  html += "</body></html>";
  server.send(200, "text/html", html);
}

Note that to generate the buttons, we use if and else statements to display the correct button and state according to the current state of the GPIOs.

// Display GPIO 26 controls
html += "<p>GPIO 26 - State " + output26State + "</p>";
if (output26State == "off") {
    html += "<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>";
} else {
    html += "<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>";
}

// Display GPIO 27 controls
html += "<p>GPIO 27 - State " + output27State + "</p>";
if (output27State == "off") {
    html += "<p><a href=\"/27/on\"><button class=\"button\">ON</button></a></p>";
} else {
    html += "<p><a href=\"/27/off\"><button class=\"button button2\">OFF</button></a></p>";
}

Finally, the web page is sent to the client:

server.send(200, "text/html", HTML);

3) Add Authentication to your Web Server

In some projects, you might want to keep your ESP32 web server protected with username and password. The following code protects access to the web server with a username and password.

/*  
  Rui Santos & Sara Santos - Random Nerd Tutorials
  https://RandomNerdTutorials.com/esp32-web-server-beginners-guide/
  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 <WebServer.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Username and password for web page access
const char* http_username = "admin";
const char* http_password = "admin";

// Assign output variables to GPIO pins
const int output26 = 26;
const int output27 = 27;
String output26State = "off";
String output27State = "off";

// Create a web server object
WebServer server(80);

// Function to authenticate user
bool isAuthenticated() {
  if (!server.authenticate(http_username, http_password)) {
    server.requestAuthentication();
    return false;
  }
  return true;
}

// Function to handle turning GPIO 26 on
void handleGPIO26On() {
  if (!isAuthenticated()) return;
  output26State = "on";
  digitalWrite(output26, HIGH);
  handleRoot();
}

// Function to handle turning GPIO 26 off
void handleGPIO26Off() {
  if (!isAuthenticated()) return;
  output26State = "off";
  digitalWrite(output26, LOW);
  handleRoot();
}

// Function to handle turning GPIO 27 on
void handleGPIO27On() {
  if (!isAuthenticated()) return;
  output27State = "on";
  digitalWrite(output27, HIGH);
  handleRoot();
}

// Function to handle turning GPIO 27 off
void handleGPIO27Off() {
  if (!isAuthenticated()) return;
  output27State = "off";
  digitalWrite(output27, LOW);
  handleRoot();
}

// Function to handle the root URL and show the current states
void handleRoot() {
  if (!isAuthenticated()) return;

  String html = "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
  html += "<link rel=\"icon\" href=\"data:,\">";
  html += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}";
  html += ".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}";
  html += ".button2 { background-color: #555555; }</style></head>";
  html += "<body><h1>ESP32 Web Server</h1>";

  // Display GPIO 26 controls
  html += "<p>GPIO 26 - State " + output26State + "</p>";
  if (output26State == "off") {
    html += "<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>";
  } else {
    html += "<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>";
  }

  // Display GPIO 27 controls
  html += "<p>GPIO 27 - State " + output27State + "</p>";
  if (output27State == "off") {
    html += "<p><a href=\"/27/on\"><button class=\"button\">ON</button></a></p>";
  } else {
    html += "<p><a href=\"/27/off\"><button class=\"button button2\">OFF</button></a></p>";
  }

  html += "</body></html>";
  server.send(200, "text/html", html);
}

void setup() {
  Serial.begin(115200);

  // Initialize the output variables as outputs
  pinMode(output26, OUTPUT);
  pinMode(output27, OUTPUT);
  // Set outputs to LOW
  digitalWrite(output26, LOW);
  digitalWrite(output27, LOW);

  // Connect to Wi-Fi network
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  // Set up the web server to handle different routes with authentication
  server.on("/", handleRoot);
  server.on("/26/on", handleGPIO26On);
  server.on("/26/off", handleGPIO26Off);
  server.on("/27/on", handleGPIO27On);
  server.on("/27/off", handleGPIO27Off);

  // Start the web server
  server.begin();
  Serial.println("HTTP server started");
}

void loop() {
  // Handle incoming client requests
  server.handleClient();
}

View raw code

Adding Username and Password

The following code protects your web server with username and password. By default, the username is admin, and the password is admin.

You can add the desired username and password in the following lines.

const char* http_username = "admin";
const char* http_password = "admin";

Handling Authentication and Requests

To protect your web page, we added a simple authentication mechanism. Before accessing any part of the web server, the user must enter the correct username and password. This is handled by the isAuthenticated() function.

bool isAuthenticated() {
  if (!server.authenticate(http_username, http_password)) {
    server.requestAuthentication();
    return false;
  }
  return true;
}

This function checks if the user is authenticated. If not, it prompts the user to enter the credentials. The function returns true if the user is authenticated, or false otherwise.

Before sending any response to the client, we check if the user is authenticated. Notice the use of the following line before returning anything to the client.

if (!isAuthenticated()) return;

For example, in the case of the handleGPIO26On() function:

// Function to handle turning GPIO 26 on
void handleGPIO26On() {
  if (!isAuthenticated()) return;
  output26State = "on";
  digitalWrite(output26, HIGH);
  handleRoot();
}

Testing the Web Server

Now, when you try to access your ESP32 IP address, you’ll be required to enter your username and password. Then, press the “Sign in” button:

ESP32 Web Server with Authentication

You’ll be able to access the web page to control outputs.

ESP32 Basic Web Server Example - control outputs

If you enter the wrong password, the Sign in box will show up again.


4) ESP32 Web Server Access Point (AP)

Imagine that you want to control your ESP32 using a web page on your smartphone, but the ESP32 doesn’t have access to a router to connect to the internet (so, you can’t set it as a wi-fi station like in previous examples). In that case, you can set the ESP32 as an access point.

ESP32 Web Server Access Point AP

When your ESP32 is set up as an Access Point, other devices (such as your smartphone, tablet, or computer) can connect to it without the need for a router. Let’s see how to implement that.

Set the ESP32 as an Access Point

To set the ESP32 as an access point, set the Wi-Fi mode to access point as follows:

WiFi.mode(WIFI_AP);

And then, use the softAP() method as follows:

WiFi.softAP(ssid, password);

ssid is the name you want to give to the ESP32 access point, and the password parameter is the password for the access point. If you don’t want to set a password, set it to NULL.

There are also other optional parameters you can pass to the softAP() method. Here are all the parameters:

WiFi.softAP(const char* ssid, const char* password, int channel, int ssid_hidden, int max_connection);
  • ssid: name for the access point – maximum of 63 characters;
  • password: minimum of 8 characters; set to NULL if you want the access point to be open;
  • channel: Wi-Fi channel number (1-13)
  • ssid_hidden: (0 = broadcast SSID, 1 = hide SSID)
  • max_connection: maximum simultaneous connected clients (1-4)

The following code creates the same web server built previously, but in access point mode.

/*  
  Rui Santos & Sara Santos - Random Nerd Tutorials
  https://RandomNerdTutorials.com/esp32-web-server-beginners-guide/
  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 <WebServer.h>

// Replace with SSID and PASSWORD for the ESP32 ACCESS POINT
// (for testing you can leave the default)
const char* ssid = "ESP32_ACCESS_POINT";
const char* password = "pass123456";

// Assign output variables to GPIO pins
const int output26 = 26;
const int output27 = 27;
String output26State = "off";
String output27State = "off";

// Create a web server object
WebServer server(80);

// Function to handle turning GPIO 26 on
void handleGPIO26On() {
  output26State = "on";
  digitalWrite(output26, HIGH);
  handleRoot();
}

// Function to handle turning GPIO 26 off
void handleGPIO26Off() {
  output26State = "off";
  digitalWrite(output26, LOW);
  handleRoot();
}

// Function to handle turning GPIO 27 on
void handleGPIO27On() {
  output27State = "on";
  digitalWrite(output27, HIGH);
  handleRoot();
}

// Function to handle turning GPIO 27 off
void handleGPIO27Off() {
  output27State = "off";
  digitalWrite(output27, LOW);
  handleRoot();
}

// Function to handle the root URL and show the current states
void handleRoot() {
  String html = "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
  html += "<link rel=\"icon\" href=\"data:,\">";
  html += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}";
  html += ".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}";
  html += ".button2 { background-color: #555555; }</style></head>";
  html += "<body><h1>ESP32 Web Server</h1>";

  // Display GPIO 26 controls
  html += "<p>GPIO 26 - State " + output26State + "</p>";
  if (output26State == "off") {
    html += "<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>";
  } else {
    html += "<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>";
  }

  // Display GPIO 27 controls
  html += "<p>GPIO 27 - State " + output27State + "</p>";
  if (output27State == "off") {
    html += "<p><a href=\"/27/on\"><button class=\"button\">ON</button></a></p>";
  } else {
    html += "<p><a href=\"/27/off\"><button class=\"button button2\">OFF</button></a></p>";
  }

  html += "</body></html>";
  server.send(200, "text/html", html);
}

void setup() {
  Serial.begin(115200);

  // Initialize the output variables as outputs
  pinMode(output26, OUTPUT);
  pinMode(output27, OUTPUT);
  // Set outputs to LOW
  digitalWrite(output26, LOW);
  digitalWrite(output27, LOW);

  // Set the ESP32 as access point
  Serial.print("Setting as access point ");
  WiFi.mode(WIFI_AP);
  WiFi.softAP(ssid, password);

  Serial.println("");
  Serial.println("ESP32 Wi-Fi Access Point ready!");
  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);

  // Set up the web server to handle different routes
  server.on("/", handleRoot);
  server.on("/26/on", handleGPIO26On);
  server.on("/26/off", handleGPIO26Off);
  server.on("/27/on", handleGPIO27On);
  server.on("/27/off", handleGPIO27Off);

  // Start the web server
  server.begin();
  Serial.println("HTTP server started");
}

void loop() {
  // Handle incoming client requests
  server.handleClient();
}

View raw code

You can upload the previous code to your board.

The ESP32 will set up its own Wi-Fi network. Now, to access the web server, you need to connect your computer, or smartphone to the ESP32 network.

Setting the ESP32 as an Access Point

In your smartphone, go to the Wi-Fi settings and connect to the ESP32_ACCESS_POINT. The password is pass123456 (if you left the default values). Then, open the browser and type the ESP32 IP address: 192.168.4.1.

Now, you should be able to control the ESP32 without the need for a router. You’re using the ESP32 Wi-Fi network.


5) ESP32 Web Server Example (Display Sensor Readings)

To finish our ESP32 web servers tutorial, we’ll create a simple web server example to display sensor readings. We’ll display temperature, humidity, and pressure readings from a BME280 sensor, but you can use any other sensor.

Recommended reading: ESP32 with BME280 Sensor using Arduino IDE (Pressure, Temperature, Humidity).

BME280 Temperature, Humidity, and pressure sensor

We have guides for more than 30 sensors and modules with the ESP32, check them out here: ESP32: 30+ Free Guides for Sensors and Modules.

Project Overview

In this example, we’ll display sensor readings from a BME280 on a table. The web page to display the readings will look as follows:

ESP32 web Server display BME 280 sensor readings

The temperature, humidity, pressure, and altitude values will be concatenated with the HTML string. When the ESP32 receives a request on the root URL, we’ll send the HTML string concatenated with the current sensor readings.

To get new readings, you need to refresh the web page to make a new request.

Creating a Table in HTML

In this example, we’re displaying the BME280 sensor readings on a table. So, we need to write HTML text to build a table.

To create a table in HTML you use the <table>  and </table>  tags.

To create a row, you use the <tr> and </tr> tags. The table heading is defined using the <th> and </th> tags, and each table cell is defined using the <td> and </td> tags.

To create a table for our readings, you use the following html text:

<table>
  <tr>
    <th>MEASUREMENT</th>
    <th>VALUE</th>
  </tr>
  <tr>
    <td>Temp. Celsius</td>
    <td>--- *C</td>
  </tr>
  <tr>
    <td>Temp. Fahrenheit</td>
    <td>--- *F</td>
  </tr>
  <tr>
    <td>Pressure</td>
    <td>--- hPa</td>
  </tr>
  <tr>
    <td>Approx. Altitude</td>
    <td>--- meters</td></tr>
  <tr>
    <td>Humidity</td>
    <td>--- %</td>
  </tr>
</table>

We create the header of the table with a cell called MEASUREMENT, and another named VALUE.

Then, we create six rows to display each of the readings using the <tr> and </tr>tags. Inside each row, we create two cells, using the <td> and </td> tags, one with the name of the measurement and another to hold the measurement value. The three dashes “- – -” should then be replaced with the actual measurements from the BME280 sensor.

You can save this text as table.html, drag the file into your browser, and see the result. The previous HTML text creates the following table.

ESP32 Basic HTML Table for Sensor Readings

The table doesn’t have any applied styles. You can use CSS to style the table. You can read the tutorial in the following link to learn how to style a table: CSS Styling Tables.

Wiring the Circuit

Wire the sensor to the ESP32 SDA and SCL pins, as shown in the following schematic diagram. For the ESP32 Devkit board, the default pins are GPIO 21 (SDA) and GPIO 22 (SCL). Double-check the default pins for the board you’re using.

Here’s a list of parts you need to build this circuit:

ESP32 with BME280 Wiring Diagram

Display BME280 Sensor Readings on Web Server – Code

Now that you know how to build a table to display the results and the basics of creating a web server, it’s time for the code to create it. If you’ve followed the previous examples, you’ll be familiar with most lines of code.

Copy the following code to your Arduino IDE. Before uploading, you need to insert your SSID and password.

/*  
  Rui Santos & Sara Santos - Random Nerd Tutorials
  https://RandomNerdTutorials.com/esp32-web-server-beginners-guide/
  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 <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
#include <WebServer.h>

#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme; // I2C

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Create an instance of the WebServer on port 80
WebServer server(80);

void handleRoot() {
  String html = "<!DOCTYPE html><html>";
  html += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
  html += "<link rel=\"icon\" href=\"data:,\">";
  html += "<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial;}";
  html += "table { border-collapse: collapse; width:60%; margin-left:auto; margin-right:auto; }";
  html += "th { padding: 10px; background-color: #0043af; color: white; }";
  html += "tr { border: 1px solid #ddd; padding: 10px; }";
  html += "tr:hover { background-color: #bcbcbc; }";
  html += "td { border: none; padding: 8px; }";
  html += ".sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px; }</style></head>";
  html += "<body><h1>ESP32 with BME280</h1>";
  html += "<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>";
  html += "<tr><td>Temp. Celsius</td><td><span class=\"sensor\">";
  html += String(bme.readTemperature());
  html += " *C</span></td></tr>";
  html += "<tr><td>Temp. Fahrenheit</td><td><span class=\"sensor\">";
  html += String(1.8 * bme.readTemperature() + 32);
  html += " *F</span></td></tr>";
  html += "<tr><td>Pressure</td><td><span class=\"sensor\">";
  html += String(bme.readPressure() / 100.0F);
  html += " hPa</span></td></tr>";
  html += "<tr><td>Approx. Altitude</td><td><span class=\"sensor\">";
  html += String(bme.readAltitude(SEALEVELPRESSURE_HPA));
  html += " m</span></td></tr>";
  html += "<tr><td>Humidity</td><td><span class=\"sensor\">";
  html += String(bme.readHumidity());
  html += " %</span></td></tr></table></body></html>";

  // Send the response to the client
  server.send(200, "text/html", html);
}

void setup() {
  Serial.begin(115200);

  // Initialize the BME280 sensor
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }

  // Connect to Wi-Fi
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  // Set up the routes
  server.on("/", handleRoot);
  
  // Start the server
  server.begin();
}

void loop() {
  server.handleClient();
}

View raw code

Modify the following lines to include your SSID and password.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Then, check that you have the right board and COM port selected, and upload the code to your ESP32. After uploading, open the serial monitor at a baud rate of 115200, and copy the ESP32 IP address.

Getting the ESP32 IP Address on Serial Monitor

Open your browser, paste the IP address, and you should see the latest sensor readings. To update the readings, you just need to refresh the web page.

ESP32 Display BME280 Sensor Readings on Web Server

How the code works

This sketch is very similar to the previous sketches in this tutorial. You must be familiar with most parts of the code. First, you include the WiFi and WebServer libraries to create the web server and the needed libraries to read from the BME280 sensor.

#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
#include <WebServer.h>

The following line defines a variable to save the pressure at the sea level. For more accurate altitude estimation, replace the value with the current sea level pressure at your location.

#define SEALEVELPRESSURE_HPA (1013.25)

In the following line, you create an Adafruit_BME280 object called bme that by default establishes a communication with the sensor using I2C.

Adafruit_BME280 bme; // I2C

Learn more about I2C with the ESP32: ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals (Arduino IDE).

As mentioned previously, you need to insert your ssid and password in the following lines inside the double quotes.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = " REPLACE_WITH_YOUR_PASSWORD";

Then, initialize a server on port 80.

WebServer server(80);

setup()

In the setup(), we start a serial communication at a baud rate of 115200 for debugging purposes.

Serial.begin(115200);

You check that the BME280 sensor was successfully initialized.

if (!bme.begin(0x76)) {
  Serial.println("Could not find a valid BME280 sensor, check wiring!");
  while (1);
}

The following lines begin the Wi-Fi connection with WiFi.begin(ssid, password) , wait for a successful connection, and print the ESP IP address in the serial monitor.

// Connect to Wi-Fi
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());

Finally, we set up the web server and define the routes it should handle. This web server will just handle the root URL to display the latest sensor readings. When you access the root URL, it will call the handleRoot() function.

// Set up the routes
server.on("/", handleRoot);
// Start the server
server.begin();

Displaying the Web Page

In this example, the handleRoot() function is responsible for generating the web page. It sends the HTML and CSS needed to build the page. The HTML and CSS text required to build the web page are saved on the html variable. We concatenate the current sensor readings with the html variable. This way, the HTML sent to the web browser will contain the latest sensor readings.

void handleRoot() {
  String html = "<!DOCTYPE html><html>";
  html += "<head><meta name=\"viewport\" 
           content=\"width=device-width, initial-scale=1\">";
  html += "<link rel=\"icon\" href=\"data:,\">";
  html += "<style>body { text-align: center; 
           font-family: \"Trebuchet MS\", Arial;}";
  html += "table { border-collapse: collapse; width:50%; 
           margin-left:auto; margin-right:auto; }";
  html += "th { padding: 10px; background-color: #0043af; color: white; }";
  html += "tr { border: 1px solid #ddd; padding: 12px; }";
  html += "tr:hover { background-color: #bcbcbc; }";
  html += "td { border: none; padding: 10px; }";
  html += ".sensor { color:white; font-weight: bold; 
           background-color: #bcbcbc; padding: 1px; }</style></head>";
  html += "<body><h1>ESP32 with BME280</h1>";
  html += "<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>";
  html += "<tr><td>Temp. Celsius</td><td><span class=\"sensor\">";
  html += String(bme.readTemperature());
  html += " *C</span></td></tr>";
  html += "<tr><td>Temp. Fahrenheit</td><td><span class=\"sensor\">";
  html += String(1.8 * bme.readTemperature() + 32);
  html += " *F</span></td></tr>";
  html += "<tr><td>Pressure</td><td><span class=\"sensor\">";
  html += String(bme.readPressure() / 100.0F);
  html += " hPa</span></td></tr>";
  html += "<tr><td>Approx. Altitude</td><td><span class=\"sensor\">";
  html += String(bme.readAltitude(SEALEVELPRESSURE_HPA));
  html += " m</span></td></tr>";
  html += "<tr><td>Humidity</td><td><span class=\"sensor\">";
  html += String(bme.readHumidity());
  html += " %</span></td></tr></table></body></html>";

  // Send the response to the client
  server.send(200, "text/html", html);
}

To display the sensor readings on the table, we just need to send them between the corresponding <td> and </td> tags. For example, to display the temperature:

html += "<tr><td>Temp. Celsius</td><td><span class=\"sensor\">";
html += String(bme.readTemperature());
html += " *C</span></td></tr>";

Note: the <span> tag is useful to style a particular part of a text. In this case, we’re using the <span> tag to include the sensor reading in a class called “sensor”. This is useful to style that particular part of the text using CSS.

By default, the table displays the temperature readings in both Celsius degrees and Fahrenheit.

ESP32 Display BME280 Sensor Readings on Web Server

To get the latest sensor readings, you just need to refresh the web page. To automatically update the readings, you need to learn other communication protocols like server-sent events or websockets.

To learn more in-depth about creating ESP32 web servers, we recommend taking a look at our eBook: Build Web Servers with ESP32 and ESP8266 eBook (3rd Edition).

Wrapping Up

In this tutorial, you learned about building web servers with the ESP32. We covered basic concepts, and we took a look at basic examples to get you started. We hope you’ve found this guide useful and that you’re able to modify the examples for your projects.

If you’re new to the ESP32 and web servers, we recommend the following eBooks:

Here are other tutorials you may like:



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.